ArGrigorov commited on
Commit
64cab52
·
verified ·
1 Parent(s): 9124b51

kquant source (vectorized packers) for reproducibility

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ src/agiws_neural_quant/kquant/__pycache__/_iq_tables.cpython-313.pyc filter=lfs diff=lfs merge=lfs -text
src/agiws_neural_quant/kquant/__init__.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """kquant — GGUF k-quant block-wise quantization formats (llama.cpp).
2
+
3
+ Block-wise quantization with super-block structure. Each format packs N weights
4
+ per block with a shared scale (and optionally min/d-scale).
5
+
6
+ Formats:
7
+ Q4_0 — block_size=32, fp16 scale, 4-bit values. ~4.5 bpw.
8
+ Q4_1 — block_size=32, fp16 scale + fp16 min, 4-bit values. ~5 bpw.
9
+ Q4_K — super-block 256, 8 sub-blocks of 32. 6-bit scale + 6-bit d-scale + 4-bit
10
+ values. ~4.5 bpw. _S/_M/_L variants differ in d-scale precision.
11
+ Q5_K — super-block 256, 5-bit values + 6-bit scale + 6-bit d-scale. ~5.5 bpw.
12
+ Q6_K — super-block 256, 6-bit values + 8-bit d-scale + 6-bit scale. ~6.5 bpw.
13
+ Q8_0 — block_size=32, fp16 scale, 8-bit values. ~8.5 bpw. Near-lossless.
14
+ Q2_K — super-block 256, 4-bit Q2-quants + 4-bit d-scale. ~2.6 bpw.
15
+ Q3_K — super-block 256, 3-bit values + 6-bit scale. ~3.5 bpw.
16
+
17
+ For NeuralQuant we implement the math (quantize/dequantize per block); packing
18
+ into GGUF binary layout is handled by the GGUF writer (stage 22.8 converters).
19
+ Here we store block data as tensors and dequantize on-the-fly for inference.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import torch
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Q8_0 — block_size=32, fp16 scale, int8 values. Near-lossless int8.
29
+ # ---------------------------------------------------------------------------
30
+
31
+ def quantize_q8_0_block(w: torch.Tensor) -> dict[str, torch.Tensor]:
32
+ """Quantize a block of 32 values to Q8_0.
33
+
34
+ Returns dict with 'scale' (fp32 scalar) and 'values' (int8 [32]).
35
+ scale = max(abs(w)) / 127; values = round(w / scale).
36
+ """
37
+ assert w.numel() == 32, f"Q8_0 block must be 32 elements, got {w.numel()}"
38
+ max_abs = w.abs().amax().clamp(min=1e-8)
39
+ scale = max_abs / 127.0
40
+ values = torch.clamp(torch.round(w / scale), min=-127, max=127).to(torch.int8)
41
+ return {"scale": scale.to(torch.float32), "values": values}
42
+
43
+
44
+ def dequantize_q8_0_block(scale: torch.Tensor, values: torch.Tensor) -> torch.Tensor:
45
+ """Reconstruct 32 values from Q8_0 block."""
46
+ return values.to(torch.float32) * scale.to(torch.float32)
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # Q4_0 — block_size=32, fp16 scale, 4-bit values [-8, 7].
51
+ # ---------------------------------------------------------------------------
52
+
53
+ def quantize_q4_0_block(w: torch.Tensor) -> dict[str, torch.Tensor]:
54
+ assert w.numel() == 32, f"Q4_0 block must be 32 elements, got {w.numel()}"
55
+ max_abs = w.abs().amax().clamp(min=1e-8)
56
+ scale = max_abs / 7.0 # 4-bit symmetric: [-8, 7], use 7 for scale
57
+ values = torch.clamp(torch.round(w / scale), min=-8, max=7).to(torch.int8)
58
+ return {"scale": scale.to(torch.float32), "values": values}
59
+
60
+
61
+ def dequantize_q4_0_block(scale: torch.Tensor, values: torch.Tensor) -> torch.Tensor:
62
+ return values.to(torch.float32) * scale.to(torch.float32)
63
+
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # Q4_K — super-block 256 = 8 sub-blocks of 32. 6-bit packed scale + 6-bit
67
+ # d-scale per sub-block + 4-bit values.
68
+ # ---------------------------------------------------------------------------
69
+
70
+ def quantize_q4_k_superblock(w: torch.Tensor) -> dict[str, torch.Tensor]:
71
+ """Quantize 256 values to Q4_K super-block.
72
+
73
+ Structure:
74
+ - 8 sub-blocks of 32 values, each 4-bit.
75
+ - super-block scale (fp32, derived from sub-block scales).
76
+ - per-sub-block d-scale (fp32, ratio sub-scale / super-scale).
77
+
78
+ For NeuralQuant inference we store the actual sub-block scales (not the
79
+ 6-bit packed GGUF representation); the GGUF writer (stage 22.8) handles
80
+ bit-packing.
81
+ """
82
+ assert w.numel() == 256, f"Q4_K super-block must be 256 elements, got {w.numel()}"
83
+ sub = w.reshape(8, 32)
84
+ # Per-sub-block scale: absmax / 7 (4-bit symmetric).
85
+ sub_scales = sub.abs().amax(dim=1).clamp(min=1e-8) / 7.0 # [8]
86
+ values = torch.clamp(torch.round(sub / sub_scales.unsqueeze(1)), min=-8, max=7).to(torch.int8)
87
+ # Super-block scale = max(sub_scales). d-scale = sub_scale / super_scale.
88
+ super_scale = sub_scales.amax().clamp(min=1e-8)
89
+ d_scales = sub_scales / super_scale # [8], in (0, 1]
90
+ return {
91
+ "super_scale": super_scale.to(torch.float32),
92
+ "d_scales": d_scales.to(torch.float32),
93
+ "values": values.reshape(256), # int8 [256]
94
+ }
95
+
96
+
97
+ def dequantize_q4_k_superblock(super_scale, d_scales, values) -> torch.Tensor:
98
+ """Reconstruct 256 values from Q4_K super-block."""
99
+ vals = values.to(torch.int8).reshape(8, 32)
100
+ sub_scales = super_scale.to(torch.float32) * d_scales.to(torch.float32) # [8]
101
+ return (vals.to(torch.float32) * sub_scales.unsqueeze(1)).reshape(256)
102
+
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # Q6_K — super-block 256, 6-bit values + 8-bit d-scale + 6-bit super-scale.
106
+ # ---------------------------------------------------------------------------
107
+
108
+ _Q6_LEVELS = 31 # 6-bit symmetric [-32, 31], use 31 for scale
109
+
110
+
111
+ def quantize_q6_k_superblock(w: torch.Tensor) -> dict[str, torch.Tensor]:
112
+ assert w.numel() == 256, f"Q6_K super-block must be 256 elements, got {w.numel()}"
113
+ sub = w.reshape(8, 32)
114
+ sub_scales = sub.abs().amax(dim=1).clamp(min=1e-8) / _Q6_LEVELS # [8]
115
+ values = torch.clamp(torch.round(sub / sub_scales.unsqueeze(1)), min=-32, max=31).to(torch.int8)
116
+ super_scale = sub_scales.amax().clamp(min=1e-8)
117
+ d_scales = sub_scales / super_scale # [8]
118
+ return {
119
+ "super_scale": super_scale.to(torch.float32),
120
+ "d_scales": d_scales.to(torch.float32),
121
+ "values": values.reshape(256),
122
+ }
123
+
124
+
125
+ def dequantize_q6_k_superblock(super_scale, d_scales, values) -> torch.Tensor:
126
+ vals = values.to(torch.int8).reshape(8, 32)
127
+ sub_scales = super_scale.to(torch.float32) * d_scales.to(torch.float32)
128
+ return (vals.to(torch.float32) * sub_scales.unsqueeze(1)).reshape(256)
129
+
130
+
131
+ # ---------------------------------------------------------------------------
132
+ # Q5_K — super-block 256, 5-bit values + 6-bit d-scale.
133
+ # ---------------------------------------------------------------------------
134
+
135
+ _Q5_LEVELS = 15 # 5-bit symmetric [-16, 15], use 15
136
+
137
+
138
+ def quantize_q5_k_superblock(w: torch.Tensor) -> dict[str, torch.Tensor]:
139
+ assert w.numel() == 256
140
+ sub = w.reshape(8, 32)
141
+ sub_scales = sub.abs().amax(dim=1).clamp(min=1e-8) / _Q5_LEVELS
142
+ values = torch.clamp(torch.round(sub / sub_scales.unsqueeze(1)), min=-16, max=15).to(torch.int8)
143
+ super_scale = sub_scales.amax().clamp(min=1e-8)
144
+ d_scales = sub_scales / super_scale
145
+ return {
146
+ "super_scale": super_scale.to(torch.float32),
147
+ "d_scales": d_scales.to(torch.float32),
148
+ "values": values.reshape(256),
149
+ }
150
+
151
+
152
+ def dequantize_q5_k_superblock(super_scale, d_scales, values) -> torch.Tensor:
153
+ vals = values.to(torch.int8).reshape(8, 32)
154
+ sub_scales = super_scale.to(torch.float32) * d_scales.to(torch.float32)
155
+ return (vals.to(torch.float32) * sub_scales.unsqueeze(1)).reshape(256)
156
+
157
+
158
+ # ---------------------------------------------------------------------------
159
+ # Q2_K — super-block 256, 2-bit values + 4-bit d-scale. Extreme compression.
160
+ # ---------------------------------------------------------------------------
161
+
162
+ _Q2_LEVELS = 1 # 2-bit symmetric [-2, 1], use 1 for scale (coarse)
163
+
164
+
165
+ def quantize_q2_k_superblock(w: torch.Tensor) -> dict[str, torch.Tensor]:
166
+ assert w.numel() == 256
167
+ sub = w.reshape(8, 32)
168
+ sub_scales = sub.abs().amax(dim=1).clamp(min=1e-8) / _Q2_LEVELS
169
+ values = torch.clamp(torch.round(sub / sub_scales.unsqueeze(1)), min=-2, max=1).to(torch.int8)
170
+ super_scale = sub_scales.amax().clamp(min=1e-8)
171
+ d_scales = sub_scales / super_scale
172
+ return {
173
+ "super_scale": super_scale.to(torch.float32),
174
+ "d_scales": d_scales.to(torch.float32),
175
+ "values": values.reshape(256),
176
+ }
177
+
178
+
179
+ def dequantize_q2_k_superblock(super_scale, d_scales, values) -> torch.Tensor:
180
+ vals = values.to(torch.int8).reshape(8, 32)
181
+ sub_scales = super_scale.to(torch.float32) * d_scales.to(torch.float32)
182
+ return (vals.to(torch.float32) * sub_scales.unsqueeze(1)).reshape(256)
183
+
184
+
185
+ # ---------------------------------------------------------------------------
186
+ # Q3_K — super-block 256, 3-bit values + 6-bit d-scale.
187
+ # ---------------------------------------------------------------------------
188
+
189
+ _Q3_LEVELS = 3 # 3-bit symmetric [-4, 3], use 3
190
+
191
+
192
+ def quantize_q3_k_superblock(w: torch.Tensor) -> dict[str, torch.Tensor]:
193
+ assert w.numel() == 256
194
+ sub = w.reshape(8, 32)
195
+ sub_scales = sub.abs().amax(dim=1).clamp(min=1e-8) / _Q3_LEVELS
196
+ values = torch.clamp(torch.round(sub / sub_scales.unsqueeze(1)), min=-4, max=3).to(torch.int8)
197
+ super_scale = sub_scales.amax().clamp(min=1e-8)
198
+ d_scales = sub_scales / super_scale
199
+ return {
200
+ "super_scale": super_scale.to(torch.float32),
201
+ "d_scales": d_scales.to(torch.float32),
202
+ "values": values.reshape(256),
203
+ }
204
+
205
+
206
+ def dequantize_q3_k_superblock(super_scale, d_scales, values) -> torch.Tensor:
207
+ vals = values.to(torch.int8).reshape(8, 32)
208
+ sub_scales = super_scale.to(torch.float32) * d_scales.to(torch.float32)
209
+ return (vals.to(torch.float32) * sub_scales.unsqueeze(1)).reshape(256)
210
+
211
+
212
+ # ---------------------------------------------------------------------------
213
+ # Format registry: format string -> (block quantize, block dequantize, block_size)
214
+ # ---------------------------------------------------------------------------
215
+
216
+ BLOCK_SIZE_32 = 32
217
+ BLOCK_SIZE_256 = 256
218
+
219
+ FORMATS = {
220
+ "q8_0": (quantize_q8_0_block, dequantize_q8_0_block, BLOCK_SIZE_32),
221
+ "q4_0": (quantize_q4_0_block, dequantize_q4_0_block, BLOCK_SIZE_32),
222
+ "q4_k": (quantize_q4_k_superblock, dequantize_q4_k_superblock, BLOCK_SIZE_256),
223
+ "q5_k": (quantize_q5_k_superblock, dequantize_q5_k_superblock, BLOCK_SIZE_256),
224
+ "q6_k": (quantize_q6_k_superblock, dequantize_q6_k_superblock, BLOCK_SIZE_256),
225
+ "q2_k": (quantize_q2_k_superblock, dequantize_q2_k_superblock, BLOCK_SIZE_256),
226
+ "q3_k": (quantize_q3_k_superblock, dequantize_q3_k_superblock, BLOCK_SIZE_256),
227
+ }
228
+
229
+
230
+ def quantize_blocks(w: torch.Tensor, fmt: str) -> dict[str, torch.Tensor]:
231
+ """Quantize a flat weight tensor into blocks of the given format.
232
+
233
+ Pads the last dim to be divisible by block_size.
234
+
235
+ Returns dict with stacked block tensors.
236
+ """
237
+ quant_fn, _, block_size = FORMATS[fmt]
238
+ out_features = w.shape[0]
239
+ in_features = w.shape[1] if w.dim() > 1 else w.numel()
240
+ if w.dim() > 1:
241
+ flat = w
242
+ else:
243
+ flat = w.reshape(1, -1)
244
+ out_features = 1
245
+ # Pad in_features to be divisible by block_size.
246
+ pad = (block_size - (flat.shape[1] % block_size)) % block_size
247
+ if pad > 0:
248
+ flat = torch.nn.functional.pad(flat, (0, pad))
249
+ in_padded = flat.shape[1]
250
+ num_blocks = in_padded // block_size
251
+
252
+ # Reshape to [out, num_blocks, block_size].
253
+ blocks = flat.reshape(out_features, num_blocks, block_size)
254
+
255
+ if fmt in ("q8_0", "q4_0"):
256
+ # Simple block: per-block absmax scale + int values.
257
+ n_levels = 127 if fmt == "q8_0" else 7
258
+ max_val = n_levels if fmt == "q8_0" else 7
259
+ min_val = -n_levels if fmt == "q8_0" else -8
260
+ scales = blocks.abs().amax(dim=2).clamp(min=1e-8) / n_levels # [out, num_blocks]
261
+ values = torch.clamp(
262
+ torch.round(blocks / scales.unsqueeze(2)), min=min_val, max=max_val
263
+ ).to(torch.int8)
264
+ return {
265
+ "scales": scales.to(torch.float32),
266
+ "values": values,
267
+ "in_features": in_features,
268
+ "in_padded": in_padded,
269
+ "out_features": out_features,
270
+ "block_size": block_size,
271
+ }
272
+ else:
273
+ # K-formats: super-block 256 = 8 sub-blocks of 32.
274
+ # blocks already [out, num_blocks, 256]; reshape to [out, num_blocks, 8, 32].
275
+ n_levels_map = {"q4_k": 7, "q5_k": 15, "q6_k": 31, "q2_k": 1, "q3_k": 3}
276
+ min_val_map = {"q4_k": -8, "q5_k": -16, "q6_k": -32, "q2_k": -2, "q3_k": -4}
277
+ n_levels = n_levels_map[fmt]
278
+ min_val = min_val_map[fmt]
279
+ sub = blocks.reshape(out_features, num_blocks, 8, 32)
280
+ sub_scales = sub.abs().amax(dim=3).clamp(min=1e-8) / n_levels # [out, num_blocks, 8]
281
+ values = torch.clamp(
282
+ torch.round(sub / sub_scales.unsqueeze(3)), min=min_val, max=n_levels
283
+ ).to(torch.int8)
284
+ super_scales = sub_scales.amax(dim=2).clamp(min=1e-8) # [out, num_blocks]
285
+ d_scales = sub_scales / super_scales.unsqueeze(2) # [out, num_blocks, 8]
286
+ return {
287
+ "super_scales": super_scales.to(torch.float32),
288
+ "d_scales": d_scales.to(torch.float32),
289
+ "values": values.reshape(out_features, num_blocks, 256),
290
+ "in_features": in_features,
291
+ "in_padded": in_padded,
292
+ "out_features": out_features,
293
+ "block_size": 256,
294
+ "num_super": num_blocks,
295
+ }
296
+
297
+
298
+ def dequantize_blocks(qd: dict[str, torch.Tensor], fmt: str) -> torch.Tensor:
299
+ """Reconstruct the weight tensor from block-quantized data.
300
+
301
+ Supports both symmetric (our quantize_blocks output) and asymmetric
302
+ (GGUF Q4_K/Q5_K with min offset) formats. Detection is by key presence:
303
+ - If 'mins' and 'super_min_scales' are in qd -> asymmetric:
304
+ x = super_scale * scale[i] * q[i] + super_min_scale * min[i]
305
+ - Otherwise -> symmetric:
306
+ x = super_scale * d_scale[i] * q[i]
307
+ """
308
+ in_features = qd["in_features"]
309
+ in_padded = qd["in_padded"]
310
+ out_features = qd["out_features"]
311
+ if fmt in ("q8_0", "q4_0"):
312
+ scales = qd["scales"] # [out, num_blocks]
313
+ values = qd["values"] # [out, num_blocks, block_size]
314
+ w = values.to(torch.float32) * scales.to(torch.float32).unsqueeze(2)
315
+ w = w.reshape(out_features, in_padded)[:, :in_features]
316
+ else:
317
+ super_scales = qd["super_scales"] # [out, num_blocks]
318
+ d_scales = qd["d_scales"] # [out, num_blocks, 8]
319
+ values = qd["values"] # [out, num_blocks, 256]
320
+ sub_scales = super_scales.to(torch.float32).unsqueeze(2) * d_scales.to(torch.float32)
321
+ sub = values.reshape(out_features, -1, 8, 32)
322
+ w = sub.to(torch.float32) * sub_scales.unsqueeze(3)
323
+ # Asymmetric: add min offset (GGUF Q4_K/Q5_K).
324
+ if "mins" in qd and "super_min_scales" in qd:
325
+ mins = qd["mins"] # [out, num_blocks, 8]
326
+ super_min_scales = qd["super_min_scales"] # [out, num_blocks]
327
+ sub_mins = super_min_scales.to(torch.float32).unsqueeze(2) * mins.to(torch.float32)
328
+ w = w + sub_mins.unsqueeze(3)
329
+ w = w.reshape(out_features, -1)[:, :in_features]
330
+ return w
src/agiws_neural_quant/kquant/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (17.7 kB). View file
 
src/agiws_neural_quant/kquant/__pycache__/_gguf_bits.cpython-313.pyc ADDED
Binary file (2.51 kB). View file
 
src/agiws_neural_quant/kquant/__pycache__/_iq_tables.cpython-313.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1e64273922ab23df733b43120821c6c80c72efecb8fb95eafd8f4a4193928b96
3
+ size 183305
src/agiws_neural_quant/kquant/__pycache__/gguf_pack.cpython-313.pyc ADDED
Binary file (33.2 kB). View file
 
src/agiws_neural_quant/kquant/__pycache__/gguf_packed.cpython-313.pyc ADDED
Binary file (15.9 kB). View file
 
src/agiws_neural_quant/kquant/__pycache__/gguf_packed_q2q3.cpython-313.pyc ADDED
Binary file (7.48 kB). View file
 
src/agiws_neural_quant/kquant/__pycache__/gguf_plan.cpython-313.pyc ADDED
Binary file (3.1 kB). View file
 
src/agiws_neural_quant/kquant/__pycache__/iq.cpython-313.pyc ADDED
Binary file (61.1 kB). View file
 
src/agiws_neural_quant/kquant/_gguf_bits.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared bit-twiddling helpers for GGUF k-quant packed dequantization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import torch
6
+
7
+
8
+ def f16_view_to_f32(u8: torch.Tensor) -> torch.Tensor:
9
+ """View uint8 bytes as float16 then cast to float32. u8: [..., 2] uint8."""
10
+ flat = u8.reshape(-1, 2).to(torch.int16)
11
+ val = flat[:, 0] | (flat[:, 1].to(torch.int16) << 8)
12
+ f16 = val.view(torch.float16)
13
+ return f16.to(torch.float32).reshape(u8.shape[:-1])
14
+
15
+
16
+ def unpack_scales_k4(scales: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
17
+ """Unpack 8 (scale, min) 6-bit pairs from 12 bytes. scales: [..., 12] uint8.
18
+
19
+ Returns (sc, m) each [..., 8] (6-bit values as uint8).
20
+ """
21
+ q = scales.to(torch.int32)
22
+ sh = q.shape[:-1]
23
+ sc = torch.zeros(*sh, 8, dtype=torch.int32, device=q.device)
24
+ m = torch.zeros(*sh, 8, dtype=torch.int32, device=q.device)
25
+ sc[..., 0:4] = q[..., 0:4] & 0x3F
26
+ m[..., 0:4] = q[..., 4:8] & 0x3F
27
+ sc[..., 4:8] = (q[..., 8:12] & 0x0F) | ((q[..., 0:4] >> 6) << 4)
28
+ m[..., 4:8] = (q[..., 8:12] >> 4) | ((q[..., 4:8] >> 6) << 4)
29
+ return sc.to(torch.uint8), m.to(torch.uint8)
src/agiws_neural_quant/kquant/_iq_tables.py ADDED
The diff for this file is too large to render. See raw diff
 
src/agiws_neural_quant/kquant/gguf_pack.py ADDED
@@ -0,0 +1,455 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """GGUF k-quant packers - torch vectorized, float32 -> packed GGUF bytes.
2
+
3
+ Reverse of dequant_gguf_slice (gguf_packed.py). Each packer is a direct
4
+ translation of llama.cpp ggml-quants.c quantize_row_*_ref:
5
+
6
+ Q8_0 (34 B): d(f16) + qs[32] (int8) y = d*q8 block 32
7
+ Q2_K (84 B): scales[16] + qs[64] + d(f16) + dmin(f16)
8
+ y = d*sc*q - dmin*m (q 0..3) 16 sub-blocks of 16
9
+ Q3_K (110 B): hmask[32] + qs[64] + scales[12] + d(f16)
10
+ y = d*sc*(q - (hm ? 0 : 4)) 16 sub-blocks of 16
11
+ Q4_K (144 B): d(f16) + dmin(f16) + scales[12] + qs[128]
12
+ y = d*sc*q - dmin*m (q 0..15) 8 sub-blocks of 32
13
+ Q5_K (176 B): d(f16) + dmin(f16) + scales[12] + qh[32] + qs[128]
14
+ y = d*sc*(q+16*bit5) - dmin*m 8 sub-blocks of 32
15
+ Q6_K (210 B): ql[128] + qh[64] + scales[16] + d(f16)
16
+ y = d*sc*(q6-32) 16 sub-blocks of 16
17
+
18
+ i-quants are NOT packed here - kquant/iq.py quantize_iq*_s() already returns
19
+ packed GGUF bytes.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import torch
25
+
26
+ QK_K = 256
27
+ EPS = 1e-15
28
+
29
+
30
+ def _nearest_int(x: torch.Tensor) -> torch.Tensor:
31
+ """Round half away from zero (C nearest_int / llm_rintf)."""
32
+ return torch.where(x >= 0, torch.floor(x + 0.5), torch.ceil(x - 0.5))
33
+
34
+
35
+ def _f16_bytes(t: torch.Tensor) -> torch.Tensor:
36
+ """fp32 [..., n] -> uint8 [..., n, 2] little-endian f16 bytes."""
37
+ u = (t.half().view(torch.int16).to(torch.int64)) & 0xFFFF
38
+ return torch.stack([u & 0xFF, (u >> 8) & 0xFF], dim=-1).to(torch.uint8)
39
+
40
+
41
+ def _cat_blocks(segments: list[torch.Tensor], out_f: int, nblk: int,
42
+ bpb: int) -> torch.Tensor:
43
+ """Concatenate per-block byte segments into one [out_f, nblk*bpb] row buffer.
44
+
45
+ Each segment is [out_f, nblk, seg_len] uint8 in the GGUF block layout;
46
+ the result is row-major [out_f, nblk*bpb] (no Python row loop).
47
+ """
48
+ return torch.cat(segments, dim=-1).reshape(out_f, nblk * bpb)
49
+
50
+
51
+ def _make_qkx2(nmax: int, x: torch.Tensor, weights: torch.Tensor, rmin: float,
52
+ rdelta: float, nstep: int, use_mad: bool
53
+ ) -> tuple[torch.Tensor, torch.Tensor]:
54
+ """Vectorized make_qkx2_quants (ggml-quants.c, line 799).
55
+
56
+ Args:
57
+ nmax: max level (15 Q4_K, 31 Q5_K, 3 Q2_K).
58
+ x: [..., n] values.
59
+ weights: [..., n] per-value weights.
60
+ Returns:
61
+ (scale, the_min) each [..., 1].
62
+ """
63
+ n = x.shape[-1]
64
+ min_v = x.min(dim=-1, keepdim=True).values
65
+ max_v = x.max(dim=-1, keepdim=True).values
66
+ min_v = torch.where(min_v > 0, torch.zeros_like(min_v), min_v) # if min > 0: min = 0
67
+
68
+ sum_w = weights.sum(dim=-1, keepdim=True)
69
+ sum_x = (weights * x).sum(dim=-1, keepdim=True)
70
+
71
+ # Initial quantization with iscale = nmax/(max-min).
72
+ iscale = nmax / (max_v - min_v).clamp_min(EPS)
73
+ scale = 1.0 / iscale
74
+ l0 = _nearest_int(iscale * (x - min_v)).clamp(0, nmax)
75
+ diff = scale * l0 + min_v - x
76
+ best_error = (weights * (diff.abs() if use_mad else diff * diff)).sum(dim=-1, keepdim=True)
77
+
78
+ # Candidate sweep: is in 0..nstep, iscale = (rmin + rdelta*is + nmax)/(max-min).
79
+ is_grid = rmin + rdelta * torch.arange(nstep + 1, device=x.device, dtype=x.dtype) + nmax
80
+ isc_all = is_grid.view(-1, *([1] * x.dim())) / (max_v - min_v).clamp_min(EPS).unsqueeze(0)
81
+ l_all = _nearest_int(isc_all * (x - min_v).unsqueeze(0)).clamp(0, nmax) # [S, ..., n]
82
+ wl = weights.unsqueeze(0) * l_all
83
+ sum_l = wl.sum(dim=-1, keepdim=True)
84
+ sum_l2 = (wl * l_all).sum(dim=-1, keepdim=True)
85
+ sum_xl = (weights.unsqueeze(0) * l_all * x.unsqueeze(0)).sum(dim=-1, keepdim=True)
86
+
87
+ D = sum_w.unsqueeze(0) * sum_l2 - sum_l * sum_l
88
+ this_scale = (sum_w.unsqueeze(0) * sum_xl - sum_x.unsqueeze(0) * sum_l) / D.clamp_min(EPS)
89
+ this_min = (sum_l2 * sum_x.unsqueeze(0) - sum_l * sum_xl) / D.clamp_min(EPS)
90
+ # if this_min > 0: this_min = 0; this_scale = sum_xl / sum_l2
91
+ pos_min = this_min > 0
92
+ this_scale = torch.where(pos_min, sum_xl / sum_l2.clamp_min(EPS), this_scale)
93
+ this_min = torch.where(pos_min, torch.zeros_like(this_min), this_min)
94
+
95
+ diff2 = this_scale * l_all + this_min - x.unsqueeze(0)
96
+ cur_error = (weights.unsqueeze(0) * (diff2.abs() if use_mad else diff2 * diff2)).sum(dim=-1, keepdim=True)
97
+
98
+ better = cur_error < best_error # [S, ..., 1]
99
+ any_better = better.any(dim=0)
100
+ best_idx = better.int().argmax(dim=0) # [..., 1]
101
+ scale = torch.where(any_better, this_scale.gather(0, best_idx.unsqueeze(0)).squeeze(0), scale)
102
+ min_f = torch.where(any_better, this_min.gather(0, best_idx.unsqueeze(0)).squeeze(0), min_v)
103
+ return scale, -min_f
104
+
105
+
106
+ def _pack_q2_q3(l: torch.Tensor) -> torch.Tensor:
107
+ """Pack 256 x 2-bit levels into 64 bytes (llama.cpp q2_K/q3_K qs layout).
108
+
109
+ qs[j/4 + l] = L[j+l] | L[j+l+32]<<2 | L[j+l+64]<<4 | L[j+l+96]<<6, j = 0, 128.
110
+ l: [..., 256] uint8 in [0, 3].
111
+ """
112
+ v = l.to(torch.int64)
113
+ lo = v[..., 0:32]; q1 = v[..., 32:64]; q2 = v[..., 64:96]; q3 = v[..., 96:128]
114
+ lo2 = v[..., 128:160]; q12 = v[..., 160:192]; q22 = v[..., 192:224]; q32 = v[..., 224:256]
115
+ first = lo | (q1 << 2) | (q2 << 4) | (q3 << 6) # [..., 32]
116
+ second = lo2 | (q12 << 2) | (q22 << 4) | (q32 << 6)
117
+ return torch.cat([first, second], dim=-1).to(torch.uint8) # [..., 64]
118
+
119
+
120
+ def _pack_scales_min_k4(sc: torch.Tensor, m: torch.Tensor) -> torch.Tensor:
121
+ """Pack 8 (scale, min) 6-bit pairs into 12 bytes (get_scale_min_k4 layout).
122
+
123
+ j<4: q[j]=sc[j], q[j+4]=m[j]; j>=4: q[j+4]=(sc&0xF)|((m&0xF)<<4),
124
+ q[j-4] |= (sc>>4)<<6, q[j] |= (m>>4)<<6.
125
+ """
126
+ sc = sc.clamp(0, 63).to(torch.int64)
127
+ m = m.clamp(0, 63).to(torch.int64)
128
+ q = torch.zeros(*sc.shape[:-1], 12, dtype=torch.int64, device=sc.device)
129
+ q[..., 0:4] = sc[..., 0:4]
130
+ q[..., 4:8] = m[..., 0:4]
131
+ q[..., 8:12] = (sc[..., 4:8] & 0xF) | ((m[..., 4:8] & 0xF) << 4)
132
+ q[..., 0:4] |= (sc[..., 4:8] >> 4) << 6
133
+ q[..., 4:8] |= (m[..., 4:8] >> 4) << 6
134
+ return q.to(torch.uint8)
135
+
136
+
137
+ def _unpack_scales_min_k4(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
138
+ """Mirror of _gguf_bits.unpack_scales_k4 for packed [..., 12] int64 bytes."""
139
+ q = q.to(torch.int64)
140
+ sc = torch.zeros(*q.shape[:-1], 8, dtype=torch.int64, device=q.device)
141
+ m = torch.zeros_like(sc)
142
+ sc[..., 0:4] = q[..., 0:4] & 0x3F
143
+ m[..., 0:4] = q[..., 4:8] & 0x3F
144
+ sc[..., 4:8] = (q[..., 8:12] & 0x0F) | ((q[..., 0:4] >> 6) << 4)
145
+ m[..., 4:8] = (q[..., 8:12] >> 4) | ((q[..., 4:8] >> 6) << 4)
146
+ return sc, m
147
+
148
+
149
+ def _pad(w: torch.Tensor, block: int) -> tuple[torch.Tensor, int]:
150
+ out_f, in_f = w.shape
151
+ nblk = (in_f + block - 1) // block
152
+ wp = torch.zeros(out_f, nblk * block, dtype=torch.float32, device=w.device)
153
+ wp[:, :in_f] = w
154
+ return wp, nblk
155
+
156
+
157
+ # ---------------------------------------------------------------------------
158
+ # Q8_0 (llama.cpp:276) - block 32.
159
+ # ---------------------------------------------------------------------------
160
+
161
+ def pack_q8_0(w: torch.Tensor) -> torch.Tensor:
162
+ out_f, in_f = w.shape
163
+ wp, nblk = _pad(w, 32)
164
+ x = wp.view(out_f, nblk, 32)
165
+ amax = x.abs().amax(dim=-1).clamp_min(EPS)
166
+ d = amax / 127.0
167
+ qs = _nearest_int(x / d.unsqueeze(-1)).clamp(-127, 127).to(torch.int8).view(torch.uint8)
168
+ return _cat_blocks([_f16_bytes(d), qs], out_f, nblk, 34)
169
+
170
+
171
+ # ---------------------------------------------------------------------------
172
+ # Q2_K (llama.cpp:891) - 16 sub-blocks of 16.
173
+ # ---------------------------------------------------------------------------
174
+
175
+ def pack_q2_k(w: torch.Tensor) -> torch.Tensor:
176
+ out_f, in_f = w.shape
177
+ wp, nblk = _pad(w, QK_K)
178
+ x = wp.view(out_f, nblk, 16, 16)
179
+ scale, m = _make_qkx2(3, x, x.abs(), -0.5, 0.1, 15, True) # [.., 16, 1]
180
+ scale = scale.squeeze(-1)
181
+ m = m.squeeze(-1)
182
+ max_scale = scale.amax(dim=-1).clamp_min(EPS)
183
+ max_min = m.amax(dim=-1).clamp_min(EPS)
184
+
185
+ ls = _nearest_int((15.0 / max_scale).unsqueeze(-1) * scale).clamp(0, 15).to(torch.int64)
186
+ lm = _nearest_int((15.0 / max_min).unsqueeze(-1) * m).clamp(0, 15).to(torch.int64)
187
+ scales = (ls & 0xF) | ((lm & 0xF) << 4) # [out, nb, 16]
188
+ d = max_scale / 15.0
189
+ dmin = max_min / 15.0
190
+
191
+ # Re-quantize with stored d/dmin/scales.
192
+ d_f = d.unsqueeze(-1).unsqueeze(-1)
193
+ dm_f = dmin.unsqueeze(-1).unsqueeze(-1)
194
+ sc16 = (scales & 0xF).to(torch.float32).unsqueeze(-1)
195
+ m16 = (scales >> 4).to(torch.float32).unsqueeze(-1)
196
+ l = _nearest_int((x + dm_f * m16) / (d_f * sc16).clamp_min(EPS)).clamp(0, 3).to(torch.int64)
197
+ qs = _pack_q2_q3(l.view(out_f, nblk, QK_K))
198
+
199
+ return _cat_blocks([scales.to(torch.uint8), qs,
200
+ _f16_bytes(d), _f16_bytes(dmin)],
201
+ out_f, nblk, 84)
202
+
203
+
204
+ # ---------------------------------------------------------------------------
205
+ # Q3_K (llama.cpp:1229) - 16 sub-blocks of 16.
206
+ # ---------------------------------------------------------------------------
207
+
208
+ def _make_q3_quants(x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
209
+ """Vectorized make_q3_quants(16, 4, x, L, do_rmse=true).
210
+
211
+ x: [..., 16]. Returns (scale, L) with L in [-4, 3] (int), scale [..., 1].
212
+ """
213
+ amax = x.abs().amax(dim=-1)
214
+ max_v = x.gather(-1, x.abs().argmax(dim=-1, keepdim=True)).squeeze(-1) # signed max|x| element
215
+ max_s = torch.where(amax < EPS, torch.zeros_like(max_v), max_v)
216
+ iscale = -4.0 / max_s.clamp_min(EPS)
217
+ l = _nearest_int(iscale.unsqueeze(-1) * x).clamp(-4, 3).to(torch.float32)
218
+ w_ = x * x
219
+ sumlx = (w_ * x * l).sum(dim=-1, keepdim=True)
220
+ suml2 = (w_ * l * l).sum(dim=-1, keepdim=True)
221
+ for _ in range(5):
222
+ slx = sumlx - w_ * x * l # [..., 16] per-element
223
+ sl2 = suml2 - w_ * l * l # [..., 16] per-element
224
+ new_l = _nearest_int(x * sl2 / torch.where(slx > 0, slx, torch.ones_like(slx))).clamp(-4, 3)
225
+ slx2 = slx + w_ * x * new_l # [..., 16]
226
+ sl2_new = sl2 + w_ * new_l * new_l # [..., 16]
227
+ better = (slx > 0) & (sl2_new > 0) & (slx2 * slx2 * suml2 > sumlx * sumlx * sl2_new) & (new_l != l)
228
+ l = torch.where(better, new_l, l)
229
+ # Global sums update incrementally over accepted elements.
230
+ sumlx = sumlx + (better * (slx2 - slx)).sum(dim=-1, keepdim=True)
231
+ suml2 = suml2 + (better * (sl2_new - sl2)).sum(dim=-1, keepdim=True)
232
+ scale = torch.where(suml2 > 0, sumlx / suml2.clamp_min(EPS), torch.zeros_like(sumlx))
233
+ return scale, l
234
+
235
+
236
+ def pack_q3_k(w: torch.Tensor) -> torch.Tensor:
237
+ out_f, in_f = w.shape
238
+ wp, nblk = _pad(w, QK_K)
239
+ x = wp.view(out_f, nblk, 16, 16)
240
+ scale, _l = _make_q3_quants(x) # [.., 16, 1] per sub-block
241
+
242
+ sc4 = scale.squeeze(-1) # [out, nb, 16] signed
243
+ max_sc = sc4.gather(-1, sc4.abs().argmax(dim=-1, keepdim=True)).squeeze(-1) # [out, nb]
244
+ max_sc = torch.where(sc4.abs().amax(dim=-1) < EPS, torch.zeros_like(max_sc), max_sc)
245
+
246
+ isc = -32.0 / torch.where(max_sc.abs() < EPS, torch.ones_like(max_sc), max_sc)
247
+ l8 = (_nearest_int(isc.unsqueeze(-1) * sc4).clamp(-32, 31) + 32).to(torch.int64) # [.., 16] 0..63
248
+ l4 = l8 & 0xF
249
+ hi = (l8 >> 4) & 0x3
250
+ scales = torch.zeros(*l8.shape[:-1], 12, dtype=torch.int64, device=w.device)
251
+ scales[..., 0:8] = l4[..., 0:8]
252
+ scales[..., 0:8] |= l4[..., 8:16] << 4
253
+ hib = hi.view(*hi.shape[:-1], 4, 4).transpose(-1, -2) # [.., j%4, j//4]
254
+ hi_sh = (hib << (2 * torch.arange(4, device=w.device)).view(1, 1, 1, 4)).sum(-1)
255
+ scales[..., 8:12] = hi_sh
256
+ d = -max_sc / 32.0
257
+
258
+ # Re-quantize levels with the actual stored scales (aux unpacking, same as
259
+ # dequantize_row_q3_K / dequant_q3_k_packed_rows).
260
+ s0 = (scales[..., 0] | (scales[..., 1] << 8) | (scales[..., 2] << 16) | (scales[..., 3] << 24)).to(torch.int64)
261
+ s1 = (scales[..., 4] | (scales[..., 5] << 8) | (scales[..., 6] << 16) | (scales[..., 7] << 24)).to(torch.int64)
262
+ s2 = (scales[..., 8] | (scales[..., 9] << 8) | (scales[..., 10] << 16) | (scales[..., 11] << 24)).to(torch.int64)
263
+ kmask1 = 0x03030303
264
+ kmask2 = 0x0F0F0F0F
265
+ tmp = s2
266
+ aux0 = (s0 & kmask2) | (((tmp >> 0) & kmask1) << 4)
267
+ aux1 = (s1 & kmask2) | (((tmp >> 2) & kmask1) << 4)
268
+ aux2 = ((s0 >> 4) & kmask2) | (((tmp >> 4) & kmask1) << 4)
269
+ aux3 = ((s1 >> 4) & kmask2) | (((tmp >> 6) & kmask1) << 4)
270
+ aux = torch.stack([aux0, aux1, aux2, aux3], dim=-1) # [..., 4]
271
+ byte = (aux.unsqueeze(-1) >> (8 * torch.arange(4, device=w.device))).reshape(
272
+ *scales.shape[:-1], 16) & 0xFF
273
+ sc8 = torch.where(byte >= 128, byte - 256, byte).to(torch.float32)
274
+ sc = (sc8 - 32)
275
+ dl = d.unsqueeze(-1).unsqueeze(-1) * sc.unsqueeze(-1)
276
+ dl_safe = torch.where(dl == 0, torch.ones_like(dl), dl) # C: if (!d) continue
277
+ Lq = _nearest_int(x / dl_safe).clamp(-4, 3).to(torch.int64) # [-4, 3]
278
+
279
+ # hmask: bit (j%32) set when level in 4..7.
280
+ lq_shift = Lq + 4
281
+ is_hi = (lq_shift > 3).to(torch.int64)
282
+ lq_final = (lq_shift - 4 * is_hi).reshape(out_f, nblk, QK_K)
283
+ is_hi_flat = is_hi.reshape(out_f, nblk, QK_K)
284
+ idx = torch.arange(QK_K, device=w.device)
285
+ hmask = torch.zeros(out_f, nblk, 32, dtype=torch.int64, device=w.device)
286
+ hmask.scatter_add_(-1, (idx % 32).view(1, 1, -1).expand(out_f, nblk, QK_K),
287
+ is_hi_flat << (idx // 32).view(1, 1, -1))
288
+ qs = _pack_q2_q3(lq_final)
289
+
290
+ return _cat_blocks([hmask.to(torch.uint8), qs, scales.to(torch.uint8),
291
+ _f16_bytes(d)], out_f, nblk, 110)
292
+
293
+
294
+ # ---------------------------------------------------------------------------
295
+ # Q4_K (llama.cpp:1457) - 8 sub-blocks of 32.
296
+ # ---------------------------------------------------------------------------
297
+
298
+ def pack_q4_k(w: torch.Tensor) -> torch.Tensor:
299
+ out_f, in_f = w.shape
300
+ wp, nblk = _pad(w, QK_K)
301
+ x = wp.view(out_f, nblk, 8, 32)
302
+ av_x = (x * x).mean(dim=-1, keepdim=True).sqrt() # [.., 8, 1]
303
+ weights = av_x + x.abs()
304
+ scale, m = _make_qkx2(15, x, weights, -1.0, 0.1, 20, False)
305
+ scale = scale.squeeze(-1)
306
+ m = m.squeeze(-1)
307
+ max_scale = scale.amax(dim=-1).clamp_min(EPS)
308
+ max_min = m.amax(dim=-1).clamp_min(EPS)
309
+ ls = _nearest_int((63.0 / max_scale).unsqueeze(-1) * scale).clamp(0, 63)
310
+ lm = _nearest_int((63.0 / max_min).unsqueeze(-1) * m).clamp(0, 63)
311
+ q = _pack_scales_min_k4(ls, lm) # [out, nb, 12]
312
+ d = max_scale / 63.0
313
+ dmin = max_min / 63.0
314
+
315
+ d_f = d.unsqueeze(-1).unsqueeze(-1)
316
+ dm_f = dmin.unsqueeze(-1).unsqueeze(-1)
317
+ sc, mm = _unpack_scales_min_k4(q)
318
+ dl = d_f * sc.to(torch.float32).unsqueeze(-1)
319
+ ml = dm_f * mm.to(torch.float32).unsqueeze(-1)
320
+ l = _nearest_int((x + ml) / dl.clamp_min(EPS)).clamp(0, 15).to(torch.int64)
321
+ l2 = l.reshape(out_f, nblk, 4, 2, 32)
322
+ qs = (l2[..., 0, :] | (l2[..., 1, :] << 4)).reshape(out_f, nblk, 128)
323
+
324
+ return _cat_blocks([_f16_bytes(d), _f16_bytes(dmin), q, qs],
325
+ out_f, nblk, 144)
326
+
327
+
328
+ # ---------------------------------------------------------------------------
329
+ # Q5_K (llama.cpp:1644) - 8 sub-blocks of 32.
330
+ # ---------------------------------------------------------------------------
331
+
332
+ def pack_q5_k(w: torch.Tensor) -> torch.Tensor:
333
+ out_f, in_f = w.shape
334
+ wp, nblk = _pad(w, QK_K)
335
+ x = wp.view(out_f, nblk, 8, 32)
336
+ av_x = (x * x).mean(dim=-1, keepdim=True).sqrt()
337
+ weights = av_x + x.abs()
338
+ scale, m = _make_qkx2(31, x, weights, -0.5, 0.1, 15, False)
339
+ scale = scale.squeeze(-1)
340
+ m = m.squeeze(-1)
341
+ max_scale = scale.amax(dim=-1).clamp_min(EPS)
342
+ max_min = m.amax(dim=-1).clamp_min(EPS)
343
+ ls = _nearest_int((63.0 / max_scale).unsqueeze(-1) * scale).clamp(0, 63)
344
+ lm = _nearest_int((63.0 / max_min).unsqueeze(-1) * m).clamp(0, 63)
345
+ q = _pack_scales_min_k4(ls, lm)
346
+ d = max_scale / 63.0
347
+ dmin = max_min / 63.0
348
+
349
+ d_f = d.unsqueeze(-1).unsqueeze(-1)
350
+ dm_f = dmin.unsqueeze(-1).unsqueeze(-1)
351
+ sc, mm = _unpack_scales_min_k4(q)
352
+ dl = d_f * sc.to(torch.float32).unsqueeze(-1)
353
+ ml = dm_f * mm.to(torch.float32).unsqueeze(-1)
354
+ l = _nearest_int((x + ml) / dl.clamp_min(EPS)).clamp(0, 31).to(torch.int64)
355
+
356
+ # ql + qh: for g in 0..3 (groups of 64): pair of 32-elem sub-blocks.
357
+ # Each sub-block s (0..7) contributes bit s when its level > 15.
358
+ hi_s = (l > 15).to(torch.int64) # [out, nblk, 8, 32]
359
+ qh = (hi_s * (1 << torch.arange(8, device=w.device)).view(1, 1, 8, 1)).sum(-2) # [out, nblk, 32]
360
+ lo1 = torch.where(hi_s.bool(), l - 16, l) # [out, nblk, 8, 32]
361
+ ql = (lo1[..., 0::2] | (lo1[..., 1::2] << 4)).reshape(out_f, nblk, 128)
362
+
363
+ return _cat_blocks([_f16_bytes(d), _f16_bytes(dmin), q,
364
+ qh.to(torch.uint8), ql], out_f, nblk, 176)
365
+
366
+
367
+ # ---------------------------------------------------------------------------
368
+ # Q6_K (llama.cpp:1869) - 16 sub-blocks of 16.
369
+ # ---------------------------------------------------------------------------
370
+
371
+ def _make_qx_quants_rmse(x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
372
+ """Vectorized make_qx_quants(16, 32, x, L, rmse_type=1, qw=NULL) (ggml-quants.c:628).
373
+
374
+ RMSE-weighted scale: scale = sumlx/suml2 with w = x^2.
375
+ x: [..., 16]. Returns (scale [..., 1], L in [-32, 31] as float).
376
+ """
377
+ amax = x.abs().amax(dim=-1)
378
+ max_v = x.gather(-1, x.abs().argmax(dim=-1, keepdim=True)).squeeze(-1)
379
+ max_s = torch.where(amax < EPS, torch.zeros_like(max_v), max_v)
380
+ iscale = -32.0 / max_s.clamp_min(EPS)
381
+ l = _nearest_int(iscale.unsqueeze(-1) * x).clamp(-32, 31).to(torch.float32)
382
+ w_ = x * x
383
+ sumlx = (w_ * x * l).sum(dim=-1, keepdim=True)
384
+ suml2 = (w_ * l * l).sum(dim=-1, keepdim=True)
385
+ scale = torch.where(suml2 > 0, sumlx / suml2.clamp_min(EPS), torch.zeros_like(sumlx))
386
+ return scale, l
387
+
388
+
389
+ def pack_q6_k(w: torch.Tensor) -> torch.Tensor:
390
+ out_f, in_f = w.shape
391
+ wp, nblk = _pad(w, QK_K)
392
+ x = wp.view(out_f, nblk, 16, 16)
393
+ scale, _l = _make_qx_quants_rmse(x) # [.., 16, 1]
394
+
395
+ sc4 = scale.squeeze(-1) # [out, nb, 16] signed
396
+ max_sc = sc4.gather(-1, sc4.abs().argmax(dim=-1, keepdim=True)).squeeze(-1)
397
+ max_sc = torch.where(sc4.abs().amax(dim=-1) < EPS, torch.zeros_like(max_sc), max_sc)
398
+
399
+ isc = -128.0 / torch.where(max_sc.abs() < EPS, torch.ones_like(max_sc), max_sc)
400
+ sc16 = _nearest_int(isc.unsqueeze(-1) * sc4).clamp(-127, 127).to(torch.int64)
401
+ d = -max_sc / 128.0
402
+
403
+ dl = d.unsqueeze(-1).unsqueeze(-1) * sc16.to(torch.float32).unsqueeze(-1)
404
+ dl_safe = torch.where(dl == 0, torch.ones_like(dl), dl) # C: if (!d) continue
405
+ l = (_nearest_int(x / dl_safe).clamp(-32, 31).to(torch.int64) + 32).to(torch.int64) # 0..63
406
+
407
+ lo = l & 0xF
408
+ hi = (l >> 4) & 0x3
409
+ seg = l.view(out_f, nblk, 2, 8, 16) # [.., h, 8 subs, 16]
410
+ seg_lo = seg & 0xF
411
+ seg_hi = (seg >> 4) & 0x3
412
+ ql_h = torch.stack([
413
+ seg_lo[..., 0, :] | (seg_lo[..., 4, :] << 4),
414
+ seg_lo[..., 1, :] | (seg_lo[..., 5, :] << 4),
415
+ seg_lo[..., 2, :] | (seg_lo[..., 6, :] << 4),
416
+ seg_lo[..., 3, :] | (seg_lo[..., 7, :] << 4),
417
+ ], dim=-2) # [out, nblk, 2, 4, 16]
418
+ ql = ql_h.reshape(out_f, nblk, 128)
419
+ qh_h = torch.stack([
420
+ seg_hi[..., 0, :] | (seg_hi[..., 2, :] << 2)
421
+ | (seg_hi[..., 4, :] << 4) | (seg_hi[..., 6, :] << 6),
422
+ seg_hi[..., 1, :] | (seg_hi[..., 3, :] << 2)
423
+ | (seg_hi[..., 5, :] << 4) | (seg_hi[..., 7, :] << 6),
424
+ ], dim=-2) # [out, nblk, 2, 2, 16]
425
+ qh = qh_h.reshape(out_f, nblk, 64)
426
+
427
+ return _cat_blocks([ql, qh, sc16.to(torch.uint8), _f16_bytes(d)],
428
+ out_f, nblk, 210)
429
+
430
+
431
+ _GGUF_PACK = {
432
+ 8: pack_q8_0,
433
+ 10: pack_q2_k,
434
+ 11: pack_q3_k,
435
+ 12: pack_q4_k,
436
+ 13: pack_q5_k,
437
+ 14: pack_q6_k,
438
+ }
439
+
440
+
441
+ def pack_gguf_bytes(w: torch.Tensor, gguf_dtype: int) -> torch.Tensor:
442
+ """Pack fp32 [out, in] weight into GGUF bytes for a Q*_K / Q8_0 dtype."""
443
+ fn = _GGUF_PACK.get(gguf_dtype)
444
+ if fn is None:
445
+ raise ValueError(f"pack_gguf_bytes: no packer for GGML dtype {gguf_dtype}")
446
+ return fn(w)
447
+
448
+
449
+ def packer_for(gguf_dtype: int):
450
+ """Return the packer callable for a GGML dtype, or None for i-quants."""
451
+ return _GGUF_PACK.get(gguf_dtype)
452
+
453
+
454
+ def supported_pack_dtypes() -> list[int]:
455
+ return sorted(_GGUF_PACK.keys())
src/agiws_neural_quant/kquant/gguf_packed.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """GGUF k-quant packed slice-dequantization — torch, GPU-friendly, output-row slice.
2
+
3
+ Reads packed GGUF k-quant raw bytes (uint8 [out, bytes_per_row]) and dequantizes
4
+ ONLY output rows [start:end] on-the-fly — for chunked matmul via QuantizedModule.
5
+ Weights stay packed in VRAM; only the requested row slice is unpacked to fp16/fp32.
6
+
7
+ This is the memory-efficient path: a Q3_K_S 12 GB model stays ~12 GB in VRAM,
8
+ with only a small per-chunk dequant overhead (chunk_size rows at a time).
9
+
10
+ Block layouts (from ggml-common.h, same as gguf_kquant_unpack.py):
11
+ Q8_0 (34 B): d(f16) + qs[32] (int8). y = d * q8
12
+ Q4_K (144 B): d(f16) + dmin(f16) + scales[12] + qs[128] (4-bit).
13
+ y = d * sc * q4 - dmin * m (8 sub-blocks × 32, asymmetric)
14
+ Q5_K (176 B): d(f16) + dmin(f16) + scales[12] + qh[32] + qs[128] (4-bit).
15
+ y = d * sc * (q4 + 16*bit5) - dmin * m
16
+ Q6_K (210 B): ql[128] + qh[64] + scales[16] (int8) + d(f16).
17
+ y = d * sc * (q6 - 32) (16 sub-blocks × 16, symmetric)
18
+
19
+ All operations are on torch tensors (view uint8/int8, bit ops via int16/int32
20
+ intermediate) so they run on GPU when the buffer is on cuda.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import torch
26
+
27
+ from agiws_neural_quant.kquant._gguf_bits import f16_view_to_f32, unpack_scales_k4
28
+ from agiws_neural_quant.kquant.gguf_packed_q2q3 import (
29
+ dequant_q2_k_packed_rows, dequant_q3_k_packed_rows,
30
+ )
31
+ from agiws_neural_quant.kquant.iq import (
32
+ dequant_iq2_xxs_packed_rows,
33
+ dequant_iq2_xs_packed,
34
+ dequant_iq2_s_packed,
35
+ dequant_iq3_xxs_packed,
36
+ dequant_iq3_s_packed,
37
+ dequant_iq1_s_packed,
38
+ dequant_iq1_m_packed,
39
+ dequant_iq4_nl_packed,
40
+ dequant_iq4_xs_packed,
41
+ )
42
+
43
+
44
+ # GGML dtype ids (kept here to avoid a top-level import of converters.gguf_reader
45
+ # which would create a circular import via converters/__init__ -> universal -> quantizer).
46
+ GGML_TYPE_Q8_0 = 8
47
+ GGML_TYPE_Q4_K = 12
48
+ GGML_TYPE_Q5_K = 13
49
+ GGML_TYPE_Q6_K = 14
50
+ GGML_TYPE_Q2_K = 10
51
+ GGML_TYPE_Q3_K = 11
52
+ GGML_TYPE_IQ2_XXS = 16
53
+ GGML_TYPE_IQ2_XS = 17
54
+ GGML_TYPE_IQ3_XXS = 18
55
+ GGML_TYPE_IQ1_S = 19
56
+ GGML_TYPE_IQ4_NL = 20
57
+ GGML_TYPE_IQ3_S = 21
58
+ GGML_TYPE_IQ2_S = 22
59
+ GGML_TYPE_IQ4_XS = 23
60
+ GGML_TYPE_IQ1_M = 29
61
+
62
+
63
+ # Aliases kept for any external callers / tests.
64
+ def _f16_view_to_f32(u8: torch.Tensor) -> torch.Tensor:
65
+ return f16_view_to_f32(u8)
66
+
67
+
68
+ def _unpack_scales_k4(scales: torch.Tensor):
69
+ return unpack_scales_k4(scales)
70
+
71
+
72
+ def dequant_q8_0_packed_rows(
73
+ raw: torch.Tensor, start: int, end: int, cols: int
74
+ ) -> torch.Tensor:
75
+ """Dequant Q8_0 rows [start:end]. raw: [out, bytes_per_row] uint8."""
76
+ block = 32
77
+ elem = 34
78
+ chunk = raw[start:end].to(torch.int32) # [cr, bytes_per_row]
79
+ cr = chunk.shape[0]
80
+ n_blocks = (cols + block - 1) // block
81
+ chunk = chunk.reshape(cr, n_blocks, elem)
82
+ d = _f16_view_to_f32(chunk[..., 0:2]) # [cr, n_blocks]
83
+ qs = chunk[..., 2:34].to(torch.int8).to(torch.float32) # [cr, n_blocks, 32]
84
+ y = qs * d.unsqueeze(2)
85
+ y = y.reshape(cr, n_blocks * block)[:, :cols]
86
+ return y
87
+
88
+
89
+ def dequant_q4_k_packed_rows(
90
+ raw: torch.Tensor, start: int, end: int, cols: int
91
+ ) -> torch.Tensor:
92
+ """Dequant Q4_K rows [start:end]. raw: [out, bytes_per_row] uint8."""
93
+ block = 256
94
+ elem = 144
95
+ chunk = raw[start:end].to(torch.int32)
96
+ cr = chunk.shape[0]
97
+ n_blocks = (cols + block - 1) // block
98
+ chunk = chunk.reshape(cr, n_blocks, elem)
99
+ d = _f16_view_to_f32(chunk[..., 0:2]) # [cr, n_blocks]
100
+ dmin = _f16_view_to_f32(chunk[..., 2:4]) # [cr, n_blocks]
101
+ scales = chunk[..., 4:16].to(torch.uint8) # [cr, n_blocks, 12]
102
+ qs = chunk[..., 16:144] # [cr, n_blocks, 128]
103
+ sc, m = _unpack_scales_k4(scales) # each [cr, n_blocks, 8]
104
+ sc = sc.to(torch.float32)
105
+ m = m.to(torch.float32)
106
+ qs_pairs = qs.reshape(cr, n_blocks, 4, 32)
107
+ low = (qs_pairs & 0x0F).to(torch.float32)
108
+ high = (qs_pairs >> 4).to(torch.float32)
109
+ values = torch.empty(cr, n_blocks, 8, 32, dtype=torch.float32, device=raw.device)
110
+ values[..., 0::2, :] = low
111
+ values[..., 1::2, :] = high
112
+ y = d.unsqueeze(2).unsqueeze(3) * sc.unsqueeze(3) * values \
113
+ - dmin.unsqueeze(2).unsqueeze(3) * m.unsqueeze(3)
114
+ y = y.reshape(cr, n_blocks * block)[:, :cols]
115
+ return y
116
+
117
+
118
+ def dequant_q5_k_packed_rows(
119
+ raw: torch.Tensor, start: int, end: int, cols: int
120
+ ) -> torch.Tensor:
121
+ """Dequant Q5_K rows [start:end]. raw: [out, bytes_per_row] uint8."""
122
+ block = 256
123
+ elem = 176
124
+ chunk = raw[start:end].to(torch.int32)
125
+ cr = chunk.shape[0]
126
+ n_blocks = (cols + block - 1) // block
127
+ chunk = chunk.reshape(cr, n_blocks, elem)
128
+ d = _f16_view_to_f32(chunk[..., 0:2])
129
+ dmin = _f16_view_to_f32(chunk[..., 2:4])
130
+ scales = chunk[..., 4:16].to(torch.uint8)
131
+ qh = chunk[..., 16:48] # [cr, n_blocks, 32]
132
+ qs = chunk[..., 48:176]
133
+ sc, m = _unpack_scales_k4(scales)
134
+ sc = sc.to(torch.float32)
135
+ m = m.to(torch.float32)
136
+ qs_pairs = qs.reshape(cr, n_blocks, 4, 32)
137
+ low = (qs_pairs & 0x0F).to(torch.float32)
138
+ high = (qs_pairs >> 4).to(torch.float32)
139
+ values = torch.empty(cr, n_blocks, 8, 32, dtype=torch.float32, device=raw.device)
140
+ values[..., 0::2, :] = low
141
+ values[..., 1::2, :] = high
142
+ bit5 = torch.zeros(cr, n_blocks, 8, 32, dtype=torch.float32, device=raw.device)
143
+ for k in range(4):
144
+ bit5[..., 2 * k, :] = ((qh >> (2 * k)) & 1).to(torch.float32) * 16.0
145
+ bit5[..., 2 * k + 1, :] = ((qh >> (2 * k + 1)) & 1).to(torch.float32) * 16.0
146
+ values5 = values + bit5
147
+ y = d.unsqueeze(2).unsqueeze(3) * sc.unsqueeze(3) * values5 \
148
+ - dmin.unsqueeze(2).unsqueeze(3) * m.unsqueeze(3)
149
+ y = y.reshape(cr, n_blocks * block)[:, :cols]
150
+ return y
151
+
152
+
153
+ def dequant_q6_k_packed_rows(
154
+ raw: torch.Tensor, start: int, end: int, cols: int
155
+ ) -> torch.Tensor:
156
+ """Dequant Q6_K rows [start:end]. raw: [out, bytes_per_row] uint8.
157
+
158
+ Follows dequantize_row_q6_K (llama.cpp ggml-quants.c): per half-block n,
159
+ q1/q3 come from ql low/high nibble of first 32 bytes, q2/q4 of bytes 32-64,
160
+ with scale mapping sc[is+0/1/2/3] (is = l//16) -> sub-blocks 0/4/1/2
161
+ interleaved as in the reference.
162
+ """
163
+ block = 256
164
+ elem = 210
165
+ chunk = raw[start:end].to(torch.int32)
166
+ cr = chunk.shape[0]
167
+ n_blocks = (cols + block - 1) // block
168
+ chunk = chunk.reshape(cr, n_blocks, elem)
169
+ ql = chunk[..., 0:128]
170
+ qh = chunk[..., 128:192]
171
+ sc = chunk[..., 192:208].to(torch.uint8).view(torch.int8).to(torch.float32) # [cr, n_blocks, 16]
172
+ d = _f16_view_to_f32(chunk[..., 208:210]) # [cr, n_blocks]
173
+ y = torch.zeros(cr, n_blocks, 256, dtype=torch.float32, device=raw.device)
174
+ for n in range(2):
175
+ ql_c = ql[:, :, n * 64:(n + 1) * 64]
176
+ qh_c = qh[:, :, n * 32:(n + 1) * 32]
177
+ q1 = ((ql_c[:, :, 0:32] & 0x0F) | (((qh_c >> 0) & 3) << 4)).to(torch.float32) - 32.0
178
+ q2 = ((ql_c[:, :, 32:64] & 0x0F) | (((qh_c >> 2) & 3) << 4)).to(torch.float32) - 32.0
179
+ q3 = ((ql_c[:, :, 0:32] >> 4) | (((qh_c >> 4) & 3) << 4)).to(torch.float32) - 32.0
180
+ q4 = ((ql_c[:, :, 32:64] >> 4) | (((qh_c >> 6) & 3) << 4)).to(torch.float32) - 32.0
181
+ # scale mapping per C dequantize_row_q6_K: y[l]=d*sc[is+0/2/4/6] (is=l//16),
182
+ # i.e. q1->sc[0..1], q2->sc[2..3], q3->sc[4..5], q4->sc[6..7]; sc+=8 per n.
183
+ base = n * 8
184
+ d4 = d.unsqueeze(-1)
185
+ off = n * 128
186
+ y[:, :, off + 0:off + 16] = d4 * sc[:, :, base + 0].unsqueeze(-1) * q1[:, :, 0:16]
187
+ y[:, :, off + 16:off + 32] = d4 * sc[:, :, base + 1].unsqueeze(-1) * q1[:, :, 16:32]
188
+ y[:, :, off + 32:off + 48] = d4 * sc[:, :, base + 2].unsqueeze(-1) * q2[:, :, 0:16]
189
+ y[:, :, off + 48:off + 64] = d4 * sc[:, :, base + 3].unsqueeze(-1) * q2[:, :, 16:32]
190
+ y[:, :, off + 64:off + 80] = d4 * sc[:, :, base + 4].unsqueeze(-1) * q3[:, :, 0:16]
191
+ y[:, :, off + 80:off + 96] = d4 * sc[:, :, base + 5].unsqueeze(-1) * q3[:, :, 16:32]
192
+ y[:, :, off + 96:off + 112] = d4 * sc[:, :, base + 6].unsqueeze(-1) * q4[:, :, 0:16]
193
+ y[:, :, off + 112:off + 128] = d4 * sc[:, :, base + 7].unsqueeze(-1) * q4[:, :, 16:32]
194
+ return y.reshape(cr, n_blocks * block)[:, :cols]
195
+
196
+
197
+ # Registry: GGML dtype id -> (slice dequant fn, block_size, bytes_per_block).
198
+ _GGUF_SLICE_DEQUANT = {
199
+ GGML_TYPE_Q8_0: (dequant_q8_0_packed_rows, 32, 34),
200
+ GGML_TYPE_Q4_K: (dequant_q4_k_packed_rows, 256, 144),
201
+ GGML_TYPE_Q5_K: (dequant_q5_k_packed_rows, 256, 176),
202
+ GGML_TYPE_Q6_K: (dequant_q6_k_packed_rows, 256, 210),
203
+ GGML_TYPE_Q2_K: (dequant_q2_k_packed_rows, 256, 84),
204
+ GGML_TYPE_Q3_K: (dequant_q3_k_packed_rows, 256, 110),
205
+ # i-quants (QK_K = 256; IQ4_NL uses QK4_NL = 32).
206
+ GGML_TYPE_IQ2_XXS: (dequant_iq2_xxs_packed_rows, 256, 66),
207
+ GGML_TYPE_IQ2_XS: (dequant_iq2_xs_packed, 256, 74),
208
+ GGML_TYPE_IQ2_S: (dequant_iq2_s_packed, 256, 82),
209
+ GGML_TYPE_IQ3_XXS: (dequant_iq3_xxs_packed, 256, 98),
210
+ GGML_TYPE_IQ3_S: (dequant_iq3_s_packed, 256, 110),
211
+ GGML_TYPE_IQ1_S: (dequant_iq1_s_packed, 256, 66),
212
+ GGML_TYPE_IQ1_M: (dequant_iq1_m_packed, 256, 56),
213
+ GGML_TYPE_IQ4_NL: (dequant_iq4_nl_packed, 32, 18),
214
+ GGML_TYPE_IQ4_XS: (dequant_iq4_xs_packed, 256, 136),
215
+ }
216
+
217
+
218
+ def supported_gguf_dtypes() -> set[int]:
219
+ """GGML dtype ids that support packed slice-dequant."""
220
+ return set(_GGUF_SLICE_DEQUANT.keys())
221
+
222
+
223
+ def dequant_gguf_slice(
224
+ weight_raw: torch.Tensor,
225
+ gguf_dtype: int,
226
+ start: int,
227
+ end: int,
228
+ cols: int,
229
+ ) -> torch.Tensor:
230
+ """Dequantize output rows [start:end] from packed GGUF k-quant bytes.
231
+
232
+ Args:
233
+ weight_raw: uint8 tensor [out_total, bytes_per_row] of packed GGUF data.
234
+ gguf_dtype: GGML_TYPE_* id (Q8_0, Q4_K, Q5_K, Q6_K, Q2_K, Q3_K).
235
+ start, end: output row range to dequant (0-based, end exclusive).
236
+ cols: in_features — trim the dequantized output to this many columns
237
+ (the last block may be padded beyond cols).
238
+
239
+ Returns:
240
+ fp32 tensor [end-start, cols].
241
+ """
242
+ entry = _GGUF_SLICE_DEQUANT.get(gguf_dtype)
243
+ if entry is None:
244
+ from agiws_neural_quant.converters.gguf_reader import GGML_TYPE_NAMES
245
+ raise ValueError(
246
+ f"dequant_gguf_slice: dtype id={gguf_dtype} "
247
+ f"({GGML_TYPE_NAMES.get(gguf_dtype, '?')}) not supported. "
248
+ f"Supported: {sorted(GGML_TYPE_NAMES[d] for d in _GGUF_SLICE_DEQUANT)}"
249
+ )
250
+ fn, _block, _bpb = entry
251
+ return fn(weight_raw, start, end, cols)
252
+
253
+
254
+ def bytes_per_row(gguf_dtype: int, cols: int) -> int:
255
+ """Bytes per output row for a given GGUF dtype and column count."""
256
+ entry = _GGUF_SLICE_DEQUANT.get(gguf_dtype)
257
+ if entry is None:
258
+ raise ValueError(f"bytes_per_row: unsupported dtype {gguf_dtype}")
259
+ _fn, block, bpb = entry
260
+ n_blocks = (cols + block - 1) // block
261
+ return n_blocks * bpb
262
+
263
+
264
+ __all__ = [
265
+ "dequant_gguf_slice",
266
+ "dequant_q8_0_packed_rows",
267
+ "dequant_q4_k_packed_rows",
268
+ "dequant_q5_k_packed_rows",
269
+ "dequant_q6_k_packed_rows",
270
+ "dequant_q2_k_packed_rows",
271
+ "dequant_q3_k_packed_rows",
272
+ "dequant_iq2_xxs_packed_rows",
273
+ "dequant_iq2_xs_packed",
274
+ "dequant_iq2_s_packed",
275
+ "dequant_iq3_xxs_packed",
276
+ "dequant_iq3_s_packed",
277
+ "dequant_iq1_s_packed",
278
+ "dequant_iq1_m_packed",
279
+ "dequant_iq4_nl_packed",
280
+ "dequant_iq4_xs_packed",
281
+ "supported_gguf_dtypes",
282
+ "bytes_per_row",
283
+ ]
src/agiws_neural_quant/kquant/gguf_packed_q2q3.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """GGUF Q2_K / Q3_K packed slice-dequantization (torch, output-row slice).
2
+
3
+ Exact llama.cpp layouts (ggml-common.h / ggml-quants.c):
4
+
5
+ block_q2_K (84 B): scales[16] (each byte: low nibble = 4-bit scale,
6
+ high nibble = 4-bit min)
7
+ + qs[64] (256 x 2-bit) + d (f16) + dmin (f16)
8
+ dequant (dequantize_row_q2_K): 16 sub-blocks of 16.
9
+ sub-block is: n = is//8, j = is%8; qs byte = 32*n + 16*(is%2) + l;
10
+ shift = 2*(j%4); value = (qs[byte] >> shift) & 3;
11
+ y = d*sc*q - dmin*m, sc = scales[is]&0xF, m = scales[is]>>4
12
+
13
+ block_q3_K (110 B): hmask[32] + qs[64] + scales[12] + d(f16)
14
+ dequant (dequantize_row_q3_K): 16 sub-blocks of 16.
15
+ aux unpacking of scales[12] -> 16 int8 (signed), scale = int8 - 32
16
+ sub-block is: n = is//8, j_local = (is%8)//2, half = is%2;
17
+ qs byte = 32*n + 16*half + l; shift = 2*j_local;
18
+ m_bit = 1 << (j_local + 4*n); value = q - (hm[16*half+l]&m_bit ? 0 : 4);
19
+ y = d * scale * value
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import torch
25
+
26
+ from agiws_neural_quant.kquant._gguf_bits import f16_view_to_f32
27
+
28
+ # GGML dtype ids (local - avoids circular import via converters/__init__).
29
+ GGML_TYPE_Q2_K = 10
30
+ GGML_TYPE_Q3_K = 11
31
+
32
+ QK_K = 256
33
+
34
+
35
+ def dequant_q2_k_packed_rows(
36
+ raw: torch.Tensor, start: int, end: int, cols: int
37
+ ) -> torch.Tensor:
38
+ """Dequant Q2_K rows [start:end]. raw: [out, 84*n_blocks] uint8."""
39
+ block = QK_K
40
+ elem = 84
41
+ chunk = raw[start:end].to(torch.int32)
42
+ cr = chunk.shape[0]
43
+ n_blocks = (cols + block - 1) // block
44
+ chunk = chunk.reshape(cr, n_blocks, elem)
45
+ scales = chunk[..., 0:16] # [cr, nb, 16]
46
+ qs = chunk[..., 16:80] # [cr, nb, 64]
47
+ d = f16_view_to_f32(chunk[..., 80:82]) # [cr, nb]
48
+ dmin = f16_view_to_f32(chunk[..., 82:84]) # [cr, nb]
49
+
50
+ sc4 = (scales & 0x0F).to(torch.float32) # [cr, nb, 16]
51
+ m4 = (scales >> 4).to(torch.float32)
52
+
53
+ y = torch.zeros(cr, n_blocks, QK_K, dtype=torch.float32, device=raw.device)
54
+ for is_ in range(16):
55
+ n = is_ // 8
56
+ j_local = (is_ // 2) % 4
57
+ half = is_ % 2
58
+ byte_off = 32 * n + 16 * half
59
+ shift = 2 * j_local
60
+ qb = (qs[..., byte_off:byte_off + 16] >> shift) & 0x03 # [cr, nb, 16]
61
+ sc = sc4[..., is_:is_ + 1].unsqueeze(-1) # [cr, nb, 1, 1]
62
+ mn = m4[..., is_:is_ + 1].unsqueeze(-1)
63
+ d4 = d.unsqueeze(-1).unsqueeze(-1)
64
+ dm4 = dmin.unsqueeze(-1).unsqueeze(-1)
65
+ y[..., is_ * 16:is_ * 16 + 16] = (d4 * sc * qb.unsqueeze(2) - dm4 * mn).squeeze(2)
66
+ return y.reshape(cr, n_blocks * block)[:, :cols]
67
+
68
+
69
+ def dequant_q3_k_packed_rows(
70
+ raw: torch.Tensor, start: int, end: int, cols: int
71
+ ) -> torch.Tensor:
72
+ """Dequant Q3_K rows [start:end]. raw: [out, 110*n_blocks] uint8."""
73
+ block = QK_K
74
+ elem = 110
75
+ chunk = raw[start:end].to(torch.int32)
76
+ cr = chunk.shape[0]
77
+ n_blocks = (cols + block - 1) // block
78
+ chunk = chunk.reshape(cr, n_blocks, elem)
79
+ hmask = chunk[..., 0:32] # [cr, nb, 32]
80
+ qs = chunk[..., 32:96] # [cr, nb, 64]
81
+ scales = chunk[..., 96:108] # [cr, nb, 12]
82
+ d = f16_view_to_f32(chunk[..., 108:110]) # [cr, nb]
83
+
84
+ # Unpack 12 bytes into 4 little-endian u32, then reorder (aux logic).
85
+ # aux[0..2] = scales[0:12] as 3 LE u32; aux[3] is garbage-filled in C.
86
+ s0 = (scales[..., 0] | (scales[..., 1] << 8) | (scales[..., 2] << 16) | (scales[..., 3] << 24)).to(torch.int64)
87
+ s1 = (scales[..., 4] | (scales[..., 5] << 8) | (scales[..., 6] << 16) | (scales[..., 7] << 24)).to(torch.int64)
88
+ s2 = (scales[..., 8] | (scales[..., 9] << 8) | (scales[..., 10] << 16) | (scales[..., 11] << 24)).to(torch.int64)
89
+ kmask1 = 0x03030303
90
+ kmask2 = 0x0F0F0F0F
91
+ tmp = s2
92
+ aux2 = ((s0 >> 4) & kmask2) | (((tmp >> 4) & kmask1) << 4)
93
+ aux3 = ((s1 >> 4) & kmask2) | (((tmp >> 6) & kmask1) << 4)
94
+ aux0 = (s0 & kmask2) | (((tmp >> 0) & kmask1) << 4)
95
+ aux1 = (s1 & kmask2) | (((tmp >> 2) & kmask1) << 4)
96
+ # 16 int8 scales from the 16 LE bytes of aux0..aux3.
97
+ sc8 = torch.zeros(cr, n_blocks, 16, dtype=torch.int64, device=raw.device)
98
+ for i, aux in enumerate([aux0, aux1, aux2, aux3]):
99
+ for b in range(4):
100
+ byte = (aux >> (8 * b)) & 0xFF
101
+ sc8[..., 4 * i + b] = torch.where(byte >= 128, byte - 256, byte)
102
+ sc_f = (sc8 - 32).to(torch.float32) # [cr, nb, 16]
103
+
104
+ y = torch.zeros(cr, n_blocks, QK_K, dtype=torch.float32, device=raw.device)
105
+ for is_ in range(16):
106
+ n = is_ // 8
107
+ j_local = (is_ // 2) % 4
108
+ half = is_ % 2
109
+ byte_off = 32 * n + 16 * half
110
+ shift = 2 * j_local
111
+ m_shift = j_local + 4 * n # m = 1 << m_shift
112
+ qb = ((qs[..., byte_off:byte_off + 16] >> shift) & 0x03).to(torch.float32)
113
+ hb = ((hmask[..., 16 * half:16 * half + 16] >> m_shift) & 1).to(torch.float32)
114
+ val = qb - 4.0 * (1.0 - hb)
115
+ sc = sc_f[..., is_:is_ + 1].unsqueeze(-1)
116
+ d4 = d.unsqueeze(-1).unsqueeze(-1)
117
+ y[..., is_ * 16:is_ * 16 + 16] = (d4 * sc * val.unsqueeze(2)).squeeze(2)
118
+ return y.reshape(cr, n_blocks * block)[:, :cols]
119
+
120
+
121
+ __all__ = [
122
+ "dequant_q2_k_packed_rows",
123
+ "dequant_q3_k_packed_rows",
124
+ "GGML_TYPE_Q2_K",
125
+ "GGML_TYPE_Q3_K",
126
+ ]
src/agiws_neural_quant/kquant/gguf_plan.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """UD (IQ2_M) layer plan - map GGUF tensor name -> quant dtype.
2
+
3
+ Direct translation of llama.cpp llama-quant.cpp `set_type_for_tensor` for
4
+ `LLAMA_FTYPE_MOSTLY_IQ2_M` (lines 450-529), for the dense `qwen35` arch:
5
+
6
+ token_embd -> IQ3_S
7
+ output (lm_head) -> Q5_K
8
+ attn_v -> Q4_K (n_gqa = 24/4 = 6 >= 4)
9
+ attn_output / ssm_out-> IQ3_S
10
+ ffn_down (first n/8) -> IQ3_S, rest -> IQ2_S
11
+ everything else -> IQ2_S
12
+
13
+ Small / norm / 1D tensors are handled by the caller as F32/F16 and must NOT
14
+ be listed here.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ # GGML dtype ids.
20
+ F32 = 0
21
+ F16 = 1
22
+ Q8_0 = 8
23
+ Q2_K = 10
24
+ Q5_K = 13
25
+ Q4_K = 12
26
+ IQ2_S = 22
27
+ IQ3_S = 21
28
+
29
+
30
+ def _default_dtype(name: str, i_ffn_down: int, n_ffn_down: int,
31
+ n_head: int, n_head_kv: int, ftype_iq2_m: bool) -> int:
32
+ """llama-quant.cpp set_type_for_tensor for MOSTLY_IQ2_M."""
33
+ if name == "token_embd.weight":
34
+ return IQ3_S if ftype_iq2_m else Q2_K
35
+ if name == "output.weight":
36
+ return Q5_K if ftype_iq2_m else Q8_0
37
+ if name.endswith(".attn_v.weight"):
38
+ # n_gqa >= 4 -> Q4_K; else IQ3_S (or Q2_K for non-IQ2_M).
39
+ if n_head // n_head_kv >= 4:
40
+ return Q4_K
41
+ return IQ3_S if ftype_iq2_m else Q2_K
42
+ if name.endswith(".attn_output.weight") or name.endswith(".ssm_out.weight"):
43
+ return IQ3_S if ftype_iq2_m else Q2_K
44
+ if name.endswith(".ffn_down.weight"):
45
+ if ftype_iq2_m and i_ffn_down < n_ffn_down // 8:
46
+ return IQ3_S
47
+ return IQ2_S
48
+ return IQ2_S
49
+
50
+
51
+ def make_iq2_m_plan(tensor_names: list[str], *, n_head: int, n_head_kv: int,
52
+ ftype_iq2_m: bool = True) -> dict[str, int]:
53
+ """Return {gguf_name: gguf_dtype} for the quantizable tensors.
54
+
55
+ tensor_names must be sorted in GGUF info-table order (llama.cpp walks
56
+ them in file order) so the `ffn_down` first-n/8 rule applies correctly.
57
+ """
58
+ plan: dict[str, int] = {}
59
+ n_ffn_down = sum(1 for n in tensor_names if n.endswith(".ffn_down.weight"))
60
+ i_ffn_down = 0
61
+ for name in tensor_names:
62
+ if name.endswith(".ffn_down.weight"):
63
+ plan[name] = _default_dtype(name, i_ffn_down, n_ffn_down, n_head, n_head_kv, ftype_iq2_m)
64
+ i_ffn_down += 1
65
+ else:
66
+ plan[name] = _default_dtype(name, 0, n_ffn_down, n_head, n_head_kv, ftype_iq2_m)
67
+ return plan
src/agiws_neural_quant/kquant/iq.py ADDED
@@ -0,0 +1,878 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """IQ-quant formats (llama.cpp i-quants): packed slice-dequant + quantizers.
2
+
3
+ All i-quants are codebook formats: weight values are indices into a lattice
4
+ grid; the grid entry (8 or 4 values) is scaled by a block delta and optionally
5
+ sign-flipped. Grids come from llama.cpp ggml-common.h (see _iq_tables.py).
6
+
7
+ Formats (block QK_K = 256, IQ4_NL uses QK4_NL = 32):
8
+ IQ2_XXS 2.0625 bpw d(f16) + qs[64B] = 66 B
9
+ IQ2_XS 2.3125 bpw d(f16) + qs[64B] + scales[8] = 74 B
10
+ IQ2_S 2.5625 bpw d(f16) + qs[64] + qh[8] + scales[8] = 82 B
11
+ IQ3_XXS 3.0625 bpw d(f16) + qs[96] = 98 B
12
+ IQ3_S 3.4375 bpw d(f16) + qs[64] + qh[8] + signs[32] + scales[4] = 110 B
13
+ IQ1_S 1.5625 bpw d(f16) + qs[32] + qh[16]u16 = 66 B
14
+ IQ1_M 1.75 bpw qs[32] + qh[16] + scales[8]u16 = 56 B
15
+ IQ4_NL 4.5 bpw d(f16) + qs[16] (block 32) = 18 B
16
+ IQ4_XS 4.25 bpw d(f16) + scales_h[2] + scales_l[4] + qs[128] = 136 B
17
+
18
+ Dequant follows the CPU reference (ggml-quants.c, dequantize_row_iq*_*) so
19
+ results match llama.cpp bit-for-bit on the same packed bytes. All functions
20
+ slice output rows [start:end] and are GPU-friendly (torch ops only).
21
+
22
+ Quantization (quantize_row_iq*_ref): for each 32-value group the sign pattern
23
+ is chosen so the flipped magnitude vector lies on the grid; the best scale is
24
+ found by weighted least squares over candidate scales; grid rows are looked
25
+ up via the precomputed map + neighbour lists (same as iq2xs_init_impl).
26
+
27
+ Author: AGIWS NeuralQuant team
28
+ License: Apache 2.0
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import torch
34
+
35
+ from agiws_neural_quant.kquant._iq_tables import iq_table
36
+ from agiws_neural_quant.kquant.gguf_pack import _cat_blocks
37
+
38
+ QK_K = 256
39
+ QK4_NL = 32
40
+ IQ1S_DELTA = 0.125
41
+ IQ1M_DELTA = 0.125
42
+
43
+ # GGML dtype ids (see converters/gguf_reader.py).
44
+ GGML_TYPE_IQ2_XXS = 16
45
+ GGML_TYPE_IQ2_XS = 17
46
+ GGML_TYPE_IQ3_XXS = 18
47
+ GGML_TYPE_IQ1_S = 19
48
+ GGML_TYPE_IQ4_NL = 20
49
+ GGML_TYPE_IQ3_S = 21
50
+ GGML_TYPE_IQ2_S = 22
51
+ GGML_TYPE_IQ4_XS = 23
52
+ GGML_TYPE_IQ1_M = 29
53
+
54
+ _IQ_BYTES_PER_BLOCK = {
55
+ GGML_TYPE_IQ2_XXS: 66,
56
+ GGML_TYPE_IQ2_XS: 74,
57
+ GGML_TYPE_IQ2_S: 82,
58
+ GGML_TYPE_IQ3_XXS: 98,
59
+ GGML_TYPE_IQ3_S: 110,
60
+ GGML_TYPE_IQ1_S: 66,
61
+ GGML_TYPE_IQ1_M: 56,
62
+ GGML_TYPE_IQ4_NL: 18,
63
+ GGML_TYPE_IQ4_XS: 136,
64
+ }
65
+
66
+
67
+ def bytes_per_row_iq(ggml_dtype: int, cols: int) -> int:
68
+ block = QK4_NL if ggml_dtype == GGML_TYPE_IQ4_NL else QK_K
69
+ nbl = (cols + block - 1) // block
70
+ return nbl * _IQ_BYTES_PER_BLOCK[ggml_dtype]
71
+
72
+
73
+ def _f16(u8: torch.Tensor) -> torch.Tensor:
74
+ """Interpret raw [..., 2] bytes as fp16 -> fp32 (LE). Returns [...]. """
75
+ return u8.view(torch.float16)[..., 0].to(torch.float32)
76
+
77
+
78
+ def _le_u16(u8: torch.Tensor) -> torch.Tensor:
79
+ """[..., 2] bytes -> int64 (LE)."""
80
+ return (u8[..., 0].to(torch.int64) | (u8[..., 1].to(torch.int64) << 8))
81
+
82
+
83
+ def _signs_apply(vals: torch.Tensor, signs: torch.Tensor) -> torch.Tensor:
84
+ """Flip sign of last-dim 8-tuples where the sign byte bit is set.
85
+
86
+ vals [..., 8], signs uint8 [..., 1] (last dim = 8-tuple index).
87
+ """
88
+ mask = iq_table("kmask_iq2xs").to(signs.device) # [8]
89
+ bit = (signs.unsqueeze(-1) & mask).to(vals.dtype) # [..., 1, 8] -> [..., 8]
90
+ return torch.where(bit != 0, -vals, vals)
91
+
92
+
93
+ def _ksigns(idx: torch.Tensor) -> torch.Tensor:
94
+ """128-entry ksigns table lookup."""
95
+ t = iq_table("ksigns_iq2xs").to(idx.device)
96
+ return t[idx]
97
+
98
+
99
+ # ---------------------------------------------------------------------------
100
+ # IQ2_XXS -- block 66 B: d(2) + qs(32 x u16 = 64 B)
101
+ # per 32-group ib (8 groups):
102
+ # q2 = qs[4*ib..4*ib+3] (4 u16)
103
+ # aux32 = q2[2] | q2[3]<<16 (upper half carries scale+signs)
104
+ # db = d * (0.5 + (aux32 >> 28)) * 0.25
105
+ # signs = ksigns[(aux32 >> 7*l) & 127]
106
+ # grid = iq2xxs_grid[ (u8)q2[l] ] (l=0..3) -> 8 values
107
+ # ---------------------------------------------------------------------------
108
+ def dequant_iq2_xxs_packed_rows(
109
+ raw: torch.Tensor, start: int, end: int, cols: int
110
+ ) -> torch.Tensor:
111
+ chunk = raw[start:end].to(torch.uint8) # [cr, nbl*66]
112
+ cr = chunk.shape[0]
113
+ nblk = chunk.shape[1] // 66
114
+ chunk = chunk.view(cr, nblk, 66)
115
+ d = _f16(chunk[..., 0:2]) # [cr, nblk]
116
+ qs = chunk[..., 2:66].view(cr, nblk, 32, 2) # 32 u16 LE
117
+ q2 = _le_u16(qs).view(cr, nblk, 8, 4) # [cr, nblk, 8 grp, 4 u16]
118
+ aux32 = q2[..., 2] | (q2[..., 3] << 16) # [cr, nblk, 8]
119
+ db = d.unsqueeze(-1) * (0.5 + ((aux32 >> 28) & 0xF).to(torch.float32)) * 0.25 # [cr,nblk,8]
120
+ l4 = torch.arange(4, device=raw.device)
121
+ signs7 = (aux32.unsqueeze(-1) >> (7 * l4).view(1, 1, 1, 4)) & 127 # [cr,nblk,8,4]
122
+ signs = _ksigns(signs7) # [cr,nblk,8,4]
123
+ g = iq_table("iq2xxs_grid").to(raw.device) # [256, 8] int8
124
+ q0 = q2[..., 0]
125
+ q1 = q2[..., 1]
126
+ idx = torch.stack([q0 & 0xFF, q0 >> 8, q1 & 0xFF, q1 >> 8], dim=-1) # [cr,nblk,8,4]
127
+ vals = g[idx.to(torch.int64)] # [cr,nblk,8,4,8]
128
+ vals = _signs_apply(vals, signs)
129
+ out = db.unsqueeze(-1).unsqueeze(-1) * vals # [cr,nblk,8,4,8]
130
+ return out.reshape(cr, nblk * QK_K)[:, :cols]
131
+
132
+
133
+ # ---------------------------------------------------------------------------
134
+ # IQ2_XS -- block 74 B: d(2) + qs(64 B) + scales(8)
135
+ # qs: 32 u16; per group ib (8): q2 = qs16[4*ib+il] (il 0..3)
136
+ # grid = iq2xs_grid[q2 & 511], signs = ksigns[q2 >> 9]
137
+ # db = d * (0.5 + ((scales[ib] >> 4*(il/2)) & 0xF)) * 0.25
138
+ # ---------------------------------------------------------------------------
139
+ def dequant_iq2_xs_packed(
140
+ raw: torch.Tensor, start: int, end: int, cols: int
141
+ ) -> torch.Tensor:
142
+ chunk = raw[start:end].to(torch.uint8)
143
+ cr = chunk.shape[0]
144
+ nblk = chunk.shape[1] // 74
145
+ chunk = chunk.view(cr, nblk, 74)
146
+ d = _f16(chunk[..., 0:2])
147
+ qs = chunk[..., 2:66].view(cr, nblk, 32, 2)
148
+ q2 = _le_u16(qs) # [cr, nblk, 32]
149
+ q2 = q2.view(cr, nblk, 8, 4) # [cr, nblk, 8, 4]
150
+ scales = chunk[..., 66:74] # [cr, nblk, 8]
151
+ gr = iq_table("iq2xs_grid").to(raw.device) # [512, 8]
152
+ vals = gr[(q2 & 0x1FF).to(torch.int64)] # [cr, nblk, 8, 4, 8]
153
+ signs = _ksigns((q2 >> 9).to(torch.int64))
154
+ vals = _signs_apply(vals, signs)
155
+ il = torch.arange(4, device=raw.device)
156
+ code = (scales.to(torch.int64).unsqueeze(-1) >> (4 * (il // 2)).view(1, 1, 1, 4)) & 0xF # [cr, nblk, 8, 4]
157
+ db = d.unsqueeze(-1).unsqueeze(-1) * (0.5 + code.to(torch.float32)) * 0.25 # [cr,nblk,8,4]
158
+ out = (db.unsqueeze(-1) * vals).reshape(cr, nblk * QK_K)[:, :cols]
159
+ return out
160
+
161
+
162
+ # ---------------------------------------------------------------------------
163
+ # IQ2_S -- block 82 B: d(2) + qs(64) + qh(8) + scales(8)
164
+ # per group ib (8), il 0..3:
165
+ # grid = iq2s_grid[ qs[4*ib+il] | ((qh[ib] << (8-2*il)) & 0x300) ]
166
+ # signs = qs[32 + 4*ib + il]
167
+ # db = d * (0.5 + ((scales[8] >> 4*(il/2)) & 0xF)) * 0.25
168
+ # ---------------------------------------------------------------------------
169
+ def dequant_iq2_s_packed(
170
+ raw: torch.Tensor, start: int, end: int, cols: int
171
+ ) -> torch.Tensor:
172
+ chunk = raw[start:end].to(torch.uint8)
173
+ cr = chunk.shape[0]
174
+ nblk = chunk.shape[1] // 82
175
+ chunk = chunk.view(cr, nblk, 82)
176
+ d = _f16(chunk[..., 0:2])
177
+ qs = chunk[..., 2:66].to(torch.int64) # [cr, nblk, 64]
178
+ qh = chunk[..., 66:74].to(torch.int64) # [cr, nblk, 8]
179
+ scales = chunk[..., 74:82].to(torch.int64) # [cr, nblk, 8]
180
+ il = torch.arange(4, device=raw.device)
181
+ # Layout: indexes = qs[0..31] (4 per super-group), signs = qs[32..63].
182
+ idx_b = qs[..., :32].view(cr, nblk, 8, 4)
183
+ sign_b = qs[..., 32:64].view(cr, nblk, 8, 4)
184
+ gi = idx_b + ((qh.view(cr, nblk, 8, 1) << (8 - 2 * il.view(1, 1, 1, 4))) & 0x300)
185
+ gr = iq_table("iq2s_grid").to(raw.device) # [1024, 8]
186
+ vals = gr[gi] # [cr, nblk, 8, 4, 8]
187
+ vals = _signs_apply(vals, sign_b)
188
+ code = (scales.view(cr, nblk, 8, 1) >> (4 * (il.view(1, 1, 1, 4) // 2))) & 0xF
189
+ db = d.unsqueeze(-1).unsqueeze(-1) * (0.5 + code.to(torch.float32)) * 0.25
190
+ out = (db.unsqueeze(-1) * vals).reshape(cr, nblk * QK_K)[:, :cols]
191
+ return out
192
+
193
+
194
+ # ---------------------------------------------------------------------------
195
+ # IQ3_XXS -- block 98 B: d(2) + qs(96)
196
+ # qs[0:64] = 64 grid index bytes (8 groups x 8)
197
+ # qs[64:96] = scales_and_signs (8 groups x 4 = 32 B)
198
+ # per group ib: aux32 = sas[4*ib..4*ib+3] (LE u32)
199
+ # db = d * (0.5 + (aux32 >> 28)) * 0.5
200
+ # signs = ksigns[(aux32 >> 7*il) & 127] (il 0..3)
201
+ # grid1 = iq3xxs_grid[qs[8*ib + 2*il]], grid2 = [qs[8*ib+2*il+1]]
202
+ # y[j] = db*g1[j]*sgn, y[j+4] = db*g2[j]*sgn (j 0..3)
203
+ # ---------------------------------------------------------------------------
204
+ def dequant_iq3_xxs_packed(
205
+ raw: torch.Tensor, start: int, end: int, cols: int
206
+ ) -> torch.Tensor:
207
+ chunk = raw[start:end].to(torch.uint8)
208
+ cr = chunk.shape[0]
209
+ nblk = chunk.shape[1] // 98
210
+ chunk = chunk.view(cr, nblk, 98)
211
+ d = _f16(chunk[..., 0:2])
212
+ qs = chunk[..., 2:66].to(torch.int64) # [cr, nblk, 64]
213
+ gas = chunk[..., 66:98].view(cr, nblk, 16, 2) # scales+signs 16 u16
214
+ aux = _le_u16(gas).view(cr, nblk, 8, 2) # [cr, nblk, 8 grp, 2 u16]
215
+ aux32 = (aux[..., 0] | (aux[..., 1] << 16)).to(torch.int64) # [cr, nblk, 8]
216
+ db = d.unsqueeze(-1) * (0.5 + ((aux32 >> 28) & 0xF).to(torch.float32)) * 0.5
217
+ l4 = torch.arange(4, device=raw.device)
218
+ signs7 = (aux32.unsqueeze(-1) >> (7 * l4).view(1, 1, 1, 4)) & 127 # [cr, nblk, 8, 4]
219
+ signs = _ksigns(signs7)
220
+ gr = iq_table("iq3xxs_grid").to(raw.device) # [256, 4]
221
+ idx = qs.view(cr, nblk, 8, 8) # [cr, nblk, 8 grp, 8 idx]
222
+ g1 = gr[idx[..., 0::2].to(torch.int64)] # [cr, nblk, 8, 4, 4]
223
+ g2 = gr[idx[..., 1::2].to(torch.int64)]
224
+ vals = torch.cat([g1, g2], dim=-1) # [cr, nblk, 8, 4, 8]
225
+ vals = _signs_apply(vals, signs)
226
+ out = (db.unsqueeze(-1).unsqueeze(-1) * vals).reshape(cr, nblk * QK_K)[:, :cols]
227
+ return out
228
+
229
+
230
+ # ---------------------------------------------------------------------------
231
+ # IQ3_S -- block 110 B: d(2) + qs(64) + qh(8) + signs(32) + scales(4)
232
+ # per group ib (0..7), il (0..3):
233
+ # grid1 = iq3s_grid[ qs[8*ib + 2*il] | ((qh[ib] << (8-2*il)) & 256) ]
234
+ # grid2 = iq3s_grid[ qs[8*ib + 2*il+1] | ((qh[ib] << (7-2*il)) & 256) ]
235
+ # db = d * (1 + 2*((scales[ib/2] >> 4*(ib%2)) & 0xF))
236
+ # signs = signs[4*ib + il]
237
+ # y[j] = db*grid1[j]*sgn, y[j+4] = db*grid2[j]*sgn (j 0..3)
238
+ # ---------------------------------------------------------------------------
239
+ def dequant_iq3_s_packed(
240
+ raw: torch.Tensor, start: int, end: int, cols: int
241
+ ) -> torch.Tensor:
242
+ chunk = raw[start:end].to(torch.uint8)
243
+ cr = chunk.shape[0]
244
+ nblk = chunk.shape[1] // 110
245
+ chunk = chunk.view(cr, nblk, 110)
246
+ d = _f16(chunk[..., 0:2]) # [cr, nblk]
247
+ qs = chunk[..., 2:66].to(torch.int64) # [cr, nblk, 64]
248
+ qh = chunk[..., 66:74].to(torch.int64) # [cr, nblk, 8]
249
+ signs = chunk[..., 74:106] # [cr, nblk, 32]
250
+ scales = chunk[..., 106:110].to(torch.int64) # [cr, nblk, 4]
251
+ ib = torch.arange(8, device=raw.device)
252
+ il = torch.arange(4, device=raw.device)
253
+ qs8 = qs.view(cr, nblk, 8, 8) # [cr, nblk, grp, 8 idx bytes]
254
+ qh_sel = qh.view(cr, nblk, 8, 1)
255
+ gi1 = qs8[..., 0::2] + ((qh_sel << (8 - 2 * il.view(1, 1, 1, 4))) & 256) # [cr,nblk,8,4]
256
+ gi2 = qs8[..., 1::2] + ((qh_sel << (7 - 2 * il.view(1, 1, 1, 4))) & 256)
257
+ gr = iq_table("iq3s_grid").to(raw.device) # [512, 4]
258
+ v1 = gr[gi1] # [cr, nblk, 8, 4, 4]
259
+ v2 = gr[gi2]
260
+ vals = torch.cat([v1, v2], dim=-1) # [cr, nblk, 8, 4, 8]
261
+ # db per group (8 groups: scales[ib/2], nibble by ib%2)
262
+ code = (scales.view(cr, nblk, 4).repeat_interleave(2, dim=2)
263
+ >> (4 * (ib % 2).view(1, 1, 8))) & 0xF # [cr, nblk, 8]
264
+ db = d.unsqueeze(-1) * (1 + 2 * code.to(torch.float32)) # [cr, nblk, 8]
265
+ # signs per group/il
266
+ sgn = signs.view(cr, nblk, 8, 4) # [cr, nblk, 8, 4]
267
+ vals = _signs_apply(vals, sgn)
268
+ out = db.unsqueeze(-1).unsqueeze(-1) * vals # [cr, nblk, 8, 4, 8]
269
+ return out.reshape(cr, nblk * QK_K)[:, :cols]
270
+
271
+
272
+ # ---------------------------------------------------------------------------
273
+ # IQ1_S -- block 66 B: d(2) + qs(32) + qh(16 u16)
274
+ # per 32-group ib (8):
275
+ # dl = d * (2*((qh[ib] >> 12) & 7) + 1)
276
+ # delta = qh[ib] & 0x8000 ? -IQ1S_DELTA : +IQ1S_DELTA
277
+ # grid idx = qs[4*ib+il] | (((qh[ib] >> 3*il) & 7) << 8) (il 0..3)
278
+ # grid = iq1s_grid[int8] (signed! values in {-1, 0, 1}-ish)
279
+ # y[j] = dl * (grid[j] + delta)
280
+ # ---------------------------------------------------------------------------
281
+ def dequant_iq1_s_packed(
282
+ raw: torch.Tensor, start: int, end: int, cols: int
283
+ ) -> torch.Tensor:
284
+ chunk = raw[start:end].to(torch.uint8)
285
+ cr = chunk.shape[0]
286
+ nblk = chunk.shape[1] // 66
287
+ chunk = chunk.view(cr, nblk, 66)
288
+ d = _f16(chunk[..., 0:2]) # [cr, nblk]
289
+ qs = chunk[..., 2:34].to(torch.int64) # [cr, nblk, 32]
290
+ qh = chunk[..., 34:66].view(cr, nblk, 16, 2)
291
+ qh = _le_u16(qh) # [cr, nblk, 16]
292
+ ib = torch.arange(8, device=raw.device)
293
+ il = torch.arange(4, device=raw.device)
294
+ qs8 = qs.view(cr, nblk, 8, 4)
295
+ qh8 = qh.view(cr, nblk, 8, 2) # 2 u16 per group
296
+ # per group: uses qh8[..., 0] for scale/delta, and qh8[..., 0] for index too
297
+ dl = d.unsqueeze(-1) * (2 * ((qh8[..., 0] >> 12) & 7) + 1).to(torch.float32) # [cr,nblk,8]
298
+ delta = torch.where((qh8[..., 0] & 0x8000) != 0,
299
+ -IQ1S_DELTA, IQ1S_DELTA) # [cr,nblk,8]
300
+ idx = qs8 + (((qh8[..., 0].unsqueeze(-1) >> (3 * il.view(1, 1, 1, 4))) & 7) << 8)
301
+ gr = iq_table("iq1s_grid").to(raw.device) # [2048, 8] int8 signed
302
+ vals = gr[idx].to(torch.float32) # [cr,nblk,8,4,8]
303
+ out = dl.unsqueeze(-1).unsqueeze(-1) * (vals + delta.unsqueeze(-1).unsqueeze(-1))
304
+ return out.reshape(cr, nblk * QK_K)[:, :cols]
305
+
306
+
307
+ # ---------------------------------------------------------------------------
308
+ # IQ1_M -- block 56 B: qs(32) + qh(16) + scales(8 u16)
309
+ # No fp16 d! scale is f16 packed into 4 bytes of scales.
310
+ # scale.f16 = (sc[0]>>12) | ((sc[1]>>8)&0xF0) | ((sc[2]>>4)&0xF00) | (sc[3]&0xF000)
311
+ # per group ib: dl1 = d*(2*((sc[ib/2] >> 6*(ib%2)+0) & 0x7) + 1)
312
+ # dl2 = d*(2*((sc[ib/2] >> 6*(ib%2)+3) & 0x7) + 1)
313
+ # idx[0] = qs[0] | ((qh[0]<<8)&0x700), idx[1] = qs[1] | ((qh[0]<<4)&0x700)
314
+ # idx[2] = qs[2] | ((qh[1]<<8)&0x700), idx[3] = qs[3] | ((qh[1]<<4)&0x700)
315
+ # delta[l] = qh[l/2] & (0x08 << 4*(l%2)) ? -IQ1M_DELTA : +IQ1M_DELTA
316
+ # y = dl1 * (grid[idx[l]] + delta[l]) (l 0,1) dl2 for l 2,3
317
+ # ---------------------------------------------------------------------------
318
+ def dequant_iq1_m_packed(
319
+ raw: torch.Tensor, start: int, end: int, cols: int
320
+ ) -> torch.Tensor:
321
+ chunk = raw[start:end].to(torch.uint8)
322
+ cr = chunk.shape[0]
323
+ nblk = chunk.shape[1] // 56
324
+ chunk = chunk.view(cr, nblk, 56)
325
+ qs = chunk[..., 0:32].to(torch.int64) # [cr,nblk,32]
326
+ qh = chunk[..., 32:48].to(torch.int64) # [cr,nblk,16]
327
+ sc = chunk[..., 48:56].view(cr, nblk, 4, 2)
328
+ sc = _le_u16(sc) # [cr,nblk,4]
329
+ # reassemble f16: nibbles from sc[0..3]
330
+ u = (sc[..., 0] >> 12) | ((sc[..., 1] >> 8) & 0x00F0) \
331
+ | ((sc[..., 2] >> 4) & 0x0F00) | (sc[..., 3] & 0xF000) # [cr,nblk]
332
+ d = u.to(torch.int16).view(torch.float16).to(torch.float32)
333
+ ib = torch.arange(8, device=raw.device)
334
+ il = torch.arange(4, device=raw.device)
335
+ sc8 = sc.repeat_interleave(2, dim=2) # [cr,nblk,8]
336
+ # dl1 = d*(2*((sc8[ib] >> 6*(ib%2)) & 7)+1)
337
+ dl1 = d.unsqueeze(-1) * (2 * ((sc8 >> (6 * (ib % 2).view(1, 1, 8))) & 0x7) + 1).to(torch.float32)
338
+ dl2 = d.unsqueeze(-1) * (2 * ((sc8 >> (6 * (ib % 2).view(1, 1, 8) + 3)) & 0x7) + 1).to(torch.float32)
339
+ qh2 = qh.view(cr, nblk, 8, 2) # [cr,nblk,8,2]
340
+ qs8 = qs.view(cr, nblk, 8, 4)
341
+ idx0 = qs8[..., 0] | ((qh2[..., 0] << 8) & 0x700)
342
+ idx1 = qs8[..., 1] | ((qh2[..., 0] << 4) & 0x700)
343
+ idx2 = qs8[..., 2] | ((qh2[..., 1] << 8) & 0x700)
344
+ idx3 = qs8[..., 3] | ((qh2[..., 1] << 4) & 0x700)
345
+ gr = iq_table("iq1s_grid").to(torch.int64) # [2048, 8]
346
+ g0 = gr[idx0].to(torch.float32)
347
+ g1 = gr[idx1].to(torch.float32)
348
+ g2 = gr[idx2].to(torch.float32)
349
+ g3 = gr[idx3].to(torch.float32)
350
+ delta0 = torch.where((qh2[..., 0] & 0x08) != 0, -IQ1M_DELTA, IQ1M_DELTA)
351
+ delta1 = torch.where((qh2[..., 0] & 0x80) != 0, -IQ1M_DELTA, IQ1M_DELTA)
352
+ delta2 = torch.where((qh2[..., 1] & 0x08) != 0, -IQ1M_DELTA, IQ1M_DELTA)
353
+ delta3 = torch.where((qh2[..., 1] & 0x80) != 0, -IQ1M_DELTA, IQ1M_DELTA)
354
+ out = torch.cat([
355
+ (dl1.unsqueeze(-1) * (g0 + delta0.unsqueeze(-1))).unsqueeze(-2),
356
+ (dl1.unsqueeze(-1) * (g1 + delta1.unsqueeze(-1))).unsqueeze(-2),
357
+ (dl2.unsqueeze(-1) * (g2 + delta2.unsqueeze(-1))).unsqueeze(-2),
358
+ (dl2.unsqueeze(-1) * (g3 + delta3.unsqueeze(-1))).unsqueeze(-2),
359
+ ], dim=-2) # [cr,nblk,8,4,8]
360
+ return out.reshape(cr, nblk * QK_K)[:, :cols]
361
+
362
+
363
+ # ---------------------------------------------------------------------------
364
+ # IQ4_NL -- block 18 B: d(2) + qs(16)
365
+ # y[j] = d * kvalues_iq4nl[qs[j] & 0xF], y[j+16] = d * kvalues_iq4nl[qs[j] >> 4]
366
+ # ---------------------------------------------------------------------------
367
+ def dequant_iq4_nl_packed(
368
+ raw: torch.Tensor, start: int, end: int, cols: int
369
+ ) -> torch.Tensor:
370
+ chunk = raw[start:end].to(torch.uint8)
371
+ cr = chunk.shape[0]
372
+ nblk = chunk.shape[1] // 18
373
+ chunk = chunk.view(cr, nblk, 18)
374
+ d = _f16(chunk[..., 0:2]) # [cr,nblk]
375
+ qs = chunk[..., 2:18] # [cr,nblk,16]
376
+ kv = iq_table("kvalues_iq4nl").to(raw.device) # [16]
377
+ lo = kv[qs.to(torch.int64) & 0xF] # [cr,nblk,16]
378
+ hi = kv[(qs >> 4).to(torch.int64)]
379
+ vals = torch.stack([lo, hi], dim=-1).reshape(cr, nblk, 32)
380
+ return (d.unsqueeze(-1) * vals).reshape(cr, nblk * QK4_NL)[:, :cols]
381
+
382
+
383
+ # ---------------------------------------------------------------------------
384
+ # IQ4_XS -- block 136 B: d(2) + scales_h(2) + scales_l(4) + qs(128)
385
+ # per 32-group ib (8): ls = ((scales_l[ib/2] >> 4*(ib%2)) & 0xF)
386
+ # | (((scales_h >> 2*ib) & 3) << 4)
387
+ # dl = d * (ls - 32)
388
+ # y[j] = dl * kvalues_iq4nl[qs[j] & 0xF], y[j+16] = dl * kvalues_iq4nl[qs[j] >> 4]
389
+ # ---------------------------------------------------------------------------
390
+ def dequant_iq4_xs_packed(
391
+ raw: torch.Tensor, start: int, end: int, cols: int
392
+ ) -> torch.Tensor:
393
+ chunk = raw[start:end].to(torch.uint8)
394
+ cr = chunk.shape[0]
395
+ nblk = chunk.shape[1] // 136
396
+ chunk = chunk.view(cr, nblk, 136)
397
+ d = _f16(chunk[..., 0:2])
398
+ scales_h = _le_u16(chunk[..., 2:4]) # [cr, nblk] uint16
399
+ scales_l = chunk[..., 4:8].to(torch.int64) # [cr, nblk, 4]
400
+ qs = chunk[..., 8:136] # [cr, nblk, 128]
401
+ ib = torch.arange(8, device=raw.device)
402
+ ls = (scales_l.gather(2, (ib // 2).view(1, 1, 8).expand(cr, nblk, 8))
403
+ >> (4 * (ib % 2)).view(1, 1, 8)) & 0xF
404
+ ls = ls | (((scales_h.unsqueeze(-1) >> (2 * ib).view(1, 1, 8)) & 3) << 4) # [cr,nblk,8]
405
+ dl = d.unsqueeze(-1) * (ls.to(torch.float32) - 32)
406
+ kv = iq_table("kvalues_iq4nl").to(raw.device)
407
+ qs32 = qs.view(cr, nblk, 8, 16)
408
+ lo = kv[qs32.to(torch.int64) & 0xF] # [cr,nblk,8,16]
409
+ hi = kv[(qs32 >> 4).to(torch.int64)]
410
+ vals = torch.stack([lo, hi], dim=-1).reshape(cr, nblk, 8, 32)
411
+ return (dl.unsqueeze(-1) * vals).reshape(cr, nblk * QK_K)[:, :cols]
412
+
413
+
414
+
415
+
416
+
417
+ # ===========================================================================
418
+ # Quantizers (self-consistent with the dequantizers above)
419
+ # ===========================================================================
420
+
421
+ _SCALE_CANDIDATES = 17
422
+
423
+
424
+ def _find_nearest(
425
+ x: torch.Tensor, # [M, W] fp32 (magnitudes if not signed)
426
+ grid: torch.Tensor, # [G, W] int8 grid values (W = 4 or 8)
427
+ signed: bool = False, # grid rows carry signs (IQ1-style: {-1,0,1})
428
+ ) -> tuple[torch.Tensor, torch.Tensor]:
429
+ """Best (grid row, scale) for each W-vector by weighted nearest search.
430
+
431
+ Works for any grid row width W (4 for IQ3-style grids, 8 for IQ1/IQ2).
432
+ Weighted (by |x|) squared distance to every grid row over a log-spaced
433
+ candidate-scale grid; chunked over M. Returns (idx [M] int64,
434
+ scale [M] fp32). scale is the per-element multiplier: dequant gives
435
+ y = scale * grid values (scaled by the block d later).
436
+ """
437
+ M = x.shape[0]
438
+ G = grid.shape[0]
439
+ gf = grid.to(torch.float32) # [G, W]
440
+ xabs = x.abs()
441
+ gmax = gf.abs().amax(dim=-1).max().clamp(min=1e-8)
442
+ amax = xabs.amax(dim=-1).clamp(min=1e-8) # [M]
443
+ cand = (amax / gmax).unsqueeze(-1) * torch.logspace(
444
+ -2.0, 1.0, _SCALE_CANDIDATES, device=x.device) # [M, C] ~0.01..10 x amax
445
+ w = (xabs + 1e-6) # importance weight
446
+ wnorm = w / w.sum(-1, keepdim=True).clamp(min=1e-8) # [M, W]
447
+ idx = torch.empty(M, dtype=torch.int64, device=x.device)
448
+ scale = torch.empty(M, dtype=torch.float32, device=x.device)
449
+ chunk = max(1, min(M, (1 << 21) // max(1, G)))
450
+ for s0 in range(0, M, chunk):
451
+ e = min(s0 + chunk, M)
452
+ xs = x[s0:e] # [K, W]
453
+ wn = wnorm[s0:e] # [K, W]
454
+ cs = cand[s0:e] # [K, C]
455
+ # d2[K, C, G] = sum_i wn_i * (x_i - s*g_i)^2
456
+ xg = (wn * xs) @ gf.T # [K, G]
457
+ g2w = (wn.unsqueeze(1) * gf.unsqueeze(0) ** 2).sum(-1) # [K, G]
458
+ x2w = (wn * xs * xs).sum(-1) # [K]
459
+ d2 = x2w.unsqueeze(-1).unsqueeze(-1) \
460
+ - 2 * cs.unsqueeze(-1) * xg.unsqueeze(1) \
461
+ + (cs.unsqueeze(-1) ** 2) * g2w.unsqueeze(1) # [K, C, G]
462
+ d2 = d2.clamp(min=0)
463
+ flat = d2.view(e - s0, -1)
464
+ best = flat.argmin(dim=-1) # [K]
465
+ scale_idx = best // G
466
+ grid_idx = best % G
467
+ scale[s0:e] = cs.view(e - s0, -1).gather(1, scale_idx.unsqueeze(-1)).squeeze(-1)
468
+ idx[s0:e] = grid_idx
469
+ # Refine scale by weighted least squares on the chosen row.
470
+ gv = grid[idx].to(torch.float32)
471
+ num = (w * x * gv).sum(-1)
472
+ den = (w * gv * gv).sum(-1).clamp(min=1e-8)
473
+ scale = torch.where(den > 0, num / den, scale)
474
+ return idx, scale
475
+
476
+
477
+ def _pick_signs8(x8: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
478
+ """Sign bits with even parity (mirror llama.cpp ksigns trick).
479
+
480
+ Returns (sign_bits [.., 8] int64 0/1, xabs [.., 8] fp32). For odd-popcount
481
+ vectors the smallest-magnitude element is flipped.
482
+ """
483
+ sgn = (x8 < 0).to(torch.int64)
484
+ odd = (sgn.sum(-1) % 2) == 1
485
+ if odd.any():
486
+ mn = x8.abs().argmin(-1) # [..]
487
+ o = odd.unsqueeze(-1)
488
+ m = mn.unsqueeze(-1)
489
+ pos = torch.arange(x8.shape[-1], device=x8.device).view(1, -1) == m
490
+ sgn = torch.where(o & pos, 1 - sgn, sgn)
491
+ return sgn, x8.abs()
492
+
493
+
494
+ def _ksigns_encode(sgn: torch.Tensor) -> torch.Tensor:
495
+ """sgn [.., 8] int64 bits -> ksigns table index [..] int64."""
496
+ sb = (sgn * (1 << torch.arange(8, device=sgn.device))).sum(-1) # [..] 0..255
497
+ ks = iq_table("ksigns_iq2xs").to(sgn.device) # [128] uint8
498
+ lut = torch.full((256,), -1, dtype=torch.int64, device=sgn.device)
499
+ lut[ks.to(torch.int64)] = torch.arange(128, device=sgn.device)
500
+ return lut[sb.to(torch.int64)]
501
+
502
+
503
+ def _pack_u16(v: torch.Tensor) -> torch.Tensor:
504
+ """int64 [.., N] -> [.., N, 2] LE bytes."""
505
+ return torch.stack([v & 0xFF, (v >> 8) & 0xFF], dim=-1).to(torch.uint8)
506
+
507
+
508
+ def _pack_f16(d: torch.Tensor) -> torch.Tensor:
509
+ """fp32 [N] -> [N, 2] LE bytes."""
510
+ return _pack_u16(d.to(torch.float16).view(torch.int16).to(torch.int64) & 0xFFFF)
511
+
512
+
513
+ def quantize_iq2_xxs(W: torch.Tensor) -> torch.Tensor:
514
+ """[out, in] fp32 -> IQ2_XXS bytes [out, 66*nblk] (d + 32 u16).
515
+
516
+ Block 256 = 8 super-groups x 32; each super-group = 4 8-groups.
517
+ Per super-group: 4 u16 = [idx0|idx1, idx2|idx3, aux_lo, aux_hi];
518
+ aux32 = (ls << 28) | (sgn_idx[0] << 0) | (sgn_idx[1] << 7)
519
+ | (sgn_idx[2] << 14) | (sgn_idx[3] << 21).
520
+ """
521
+ out_f, in_f = W.shape
522
+ nblk = (in_f + QK_K - 1) // QK_K
523
+ Wp = torch.zeros(out_f, nblk * QK_K, dtype=torch.float32, device=W.device)
524
+ Wp[:, :in_f] = W
525
+ x32 = Wp.view(out_f, nblk, QK_K)
526
+ sgn, xa = _pick_signs8(x32.view(-1, 8))
527
+ sgn = sgn.view(out_f, nblk, 8, 4, 8) # [out, nblk, sg, 4x8, 8]
528
+ xa = xa.view(out_f, nblk, 8, 4, 8)
529
+ grid = iq_table("iq2xxs_grid").to(W.device) # [256, 8]
530
+ idx, scale = _find_nearest(xa.reshape(-1, 8), grid) # [out*nblk*32]
531
+ idx = idx.view(out_f, nblk, 8, 4)
532
+ scale = scale.view(out_f, nblk, 8, 4)
533
+ sc_g = scale.amax(dim=-1).clamp(min=1e-8) # [out, nblk, 8] per super-group
534
+ # llama.cpp: scale is "unit" scale (for q=1); dequant gives y = (scale/8)*grid.
535
+ # Our per-8-group scale is x/grid -> unit scale = 8*scale.
536
+ d = 8.0 * sc_g.amax(dim=-1) / 31.0 # [out, nblk] per block
537
+ ls = torch.clamp(torch.round(0.5 * (8.0 * scale / d.unsqueeze(-1).unsqueeze(-1) - 1)), 0, 15).to(torch.int64)
538
+ sgn_idx = _ksigns_encode(sgn) # [out, nblk, 8, 4] 0..127
539
+ aux32 = (ls << 28) | (sgn_idx << (7 * torch.arange(4, device=W.device)).view(1, 1, 1, 4))
540
+ aux32 = aux32[..., 0] | aux32[..., 1] | aux32[..., 2] | aux32[..., 3] # [out, nblk, 8]
541
+ qb = idx.to(torch.int64) & 0xFF
542
+ q2 = (qb[..., 0] | (qb[..., 1] << 8)) & 0xFFFF # [out, nblk, 8]
543
+ q2h = (qb[..., 2] | (qb[..., 3] << 8)) & 0xFFFF
544
+ aux_lo = aux32 & 0xFFFF
545
+ aux_hi = (aux32 >> 16) & 0xFFFF
546
+ u16 = torch.stack([q2, q2h, aux_lo, aux_hi], dim=-1) # [out, nblk, 8, 4]
547
+ return _cat_blocks([_pack_f16(d),
548
+ _pack_u16(u16).reshape(out_f, nblk, 64)],
549
+ out_f, nblk, 66)
550
+
551
+
552
+ def quantize_iq2_xs(W: torch.Tensor) -> torch.Tensor:
553
+ """[out, in] fp32 -> IQ2_XS bytes [out, 74*nblk] (d + 32 u16 + scales[8]).
554
+
555
+ Each 32-group: 4 u16, each = 9-bit grid index | (7-bit sign index << 9).
556
+ scales[ib] = 2 4-bit codes (lo = sub-group<2, hi = sub-group>=2) of the
557
+ unit scale / 8.
558
+ """
559
+ out_f, in_f = W.shape
560
+ nblk = (in_f + QK_K - 1) // QK_K
561
+ Wp = torch.zeros(out_f, nblk * QK_K, dtype=torch.float32, device=W.device)
562
+ Wp[:, :in_f] = W
563
+ x32 = Wp.view(out_f, nblk, QK_K)
564
+ sgn, xa = _pick_signs8(x32.view(-1, 8))
565
+ sgn = sgn.view(out_f, nblk, 8, 4, 8)
566
+ xa = xa.view(out_f, nblk, 8, 4, 8)
567
+ grid = iq_table("iq2xs_grid").to(W.device) # [512, 8]
568
+ idx, scale = _find_nearest(xa.reshape(-1, 8), grid)
569
+ idx = idx.view(out_f, nblk, 8, 4)
570
+ scale = scale.view(out_f, nblk, 8, 4)
571
+ unit = 8.0 * scale # [out, nblk, 8, 4]
572
+ pair = unit.view(out_f, nblk, 8, 2, 2).amax(-1) # [out, nblk, 8, 2]
573
+ d = pair.amax(dim=(-1, -2)) / 31.0 # [out, nblk] per block
574
+ d4 = d.unsqueeze(-1).unsqueeze(-1) # [out, nblk, 1, 1]
575
+ code = torch.clamp(torch.round(0.5 * (pair / d4 - 1)), 0, 15).to(torch.int64)
576
+ scales = code[..., 0] | (code[..., 1] << 4) # [out, nblk, 8]
577
+ sgn_idx = _ksigns_encode(sgn) # [out, nblk, 8, 4] 0..127
578
+ q2 = (idx.to(torch.int64) & 0x1FF) | (sgn_idx << 9) # [out, nblk, 8, 4]
579
+ return _cat_blocks([_pack_f16(d),
580
+ _pack_u16(q2.reshape(out_f, nblk, 32)).reshape(out_f, nblk, 64),
581
+ scales.to(torch.uint8)],
582
+ out_f, nblk, 74)
583
+
584
+
585
+ def quantize_iq2_s(W: torch.Tensor) -> torch.Tensor:
586
+ """[out, in] fp32 -> IQ2_S bytes [out, 82*nblk].
587
+
588
+ qs[4*ib+il] = low byte of grid index, qh[ib] = 2 high bits per sub-group,
589
+ signs byte at qs[32+4*ib+il], scales[8] = 4-bit pair.
590
+ """
591
+ out_f, in_f = W.shape
592
+ nblk = (in_f + QK_K - 1) // QK_K
593
+ Wp = torch.zeros(out_f, nblk * QK_K, dtype=torch.float32, device=W.device)
594
+ Wp[:, :in_f] = W
595
+ x32 = Wp.view(out_f, nblk, QK_K)
596
+ sgn, xa = _pick_signs8(x32.view(-1, 8))
597
+ sgn = sgn.view(out_f, nblk, 8, 4, 8)
598
+ xa = xa.view(out_f, nblk, 8, 4, 8)
599
+ grid = iq_table("iq2s_grid").to(W.device) # [1024, 8]
600
+ idx, scale = _find_nearest(xa.reshape(-1, 8), grid)
601
+ idx = idx.view(out_f, nblk, 8, 4)
602
+ scale = scale.view(out_f, nblk, 8, 4)
603
+ unit = 8.0 * scale
604
+ unit_p = unit.view(out_f, nblk, 8, 2, 2).amax(-1) # [out, nblk, 8, 2]
605
+ d = unit_p.amax(dim=(-1, -2)) / 31.0
606
+ d4 = d.unsqueeze(-1).unsqueeze(-1)
607
+ code = torch.clamp(torch.round(0.5 * (unit_p / d4 - 1)), 0, 15).to(torch.int64)
608
+ scales = code[..., 0] | (code[..., 1] << 4)
609
+ # signs: plain 8-bit byte (NOT ksigns!) at qs[32+4*ib+il]
610
+ signs_b = (sgn * (1 << torch.arange(8, device=W.device))).sum(-1).to(torch.uint8)
611
+ qs_low = (idx.to(torch.int64) & 0xFF).to(torch.uint8)
612
+ qs_hi = signs_b
613
+ qh = ((idx.to(torch.int64) >> 8) << (2 * torch.arange(4, device=W.device)).view(1, 1, 1, 4)).sum(-1) & 0xFF
614
+ return _cat_blocks([_pack_f16(d), qs_low.reshape(out_f, nblk, 32),
615
+ qs_hi.reshape(out_f, nblk, 32), qh,
616
+ scales.to(torch.uint8)],
617
+ out_f, nblk, 82)
618
+
619
+
620
+ def quantize_iq3_xxs(W: torch.Tensor) -> torch.Tensor:
621
+ """[out, in] fp32 -> IQ3_XXS bytes [out, 98*nblk].
622
+
623
+ qs[0:64] = 64 grid-index bytes (8 groups x 8), qs[64:96] = scales+signs
624
+ (8 groups x 4 u16 = aux32 per group). grid = iq3xxs_grid [256, 4].
625
+ """
626
+ out_f, in_f = W.shape
627
+ nblk = (in_f + QK_K - 1) // QK_K
628
+ Wp = torch.zeros(out_f, nblk * QK_K, dtype=torch.float32, device=W.device)
629
+ Wp[:, :in_f] = W
630
+ x32 = Wp.view(out_f, nblk, QK_K)
631
+ sgn, xa = _pick_signs8(x32.view(-1, 8))
632
+ sgn = sgn.view(out_f, nblk, 8, 4, 8)
633
+ xa = xa.view(out_f, nblk, 8, 4, 8)
634
+ grid = iq_table("iq3xxs_grid").to(W.device) # [256, 4]
635
+ # Each 8-group = 2 interleaved 4-halves (lo at idx[2*il], hi at idx[2*il+1]).
636
+ xa_4 = xa.view(out_f, nblk, 8, 4, 2, 4) # [.,.,8,4,2 halves,4]
637
+ xa_lo = xa_4[..., 0, :].reshape(-1, 4)
638
+ xa_hi = xa_4[..., 1, :].reshape(-1, 4)
639
+ idx_lo, s_lo = _find_nearest(xa_lo, grid)
640
+ idx_hi, s_hi = _find_nearest(xa_hi, grid)
641
+ scale = torch.maximum(s_lo, s_hi).view(out_f, nblk, 8, 4)
642
+ idx = (idx_lo.view(out_f, nblk, 8, 4).to(torch.int64)
643
+ | (idx_hi.view(out_f, nblk, 8, 4).to(torch.int64) << 8)) # 2 x 8-bit per sub-group
644
+ # per-32-group scale (4 sub-groups): max over the 4
645
+ sg = scale.amax(dim=-1).clamp(min=1e-8) # [out, nblk, 8]
646
+ d = sg.amax(dim=-1) / 15.0 # [out, nblk]
647
+ d4 = d.unsqueeze(-1).unsqueeze(-1)
648
+ # db = d*(0.5+(aux>>28))*0.5 -> aux = 4*scale/d - 1... solve: aux = round(scale/d/0.5 - 0.5)
649
+ ls = torch.clamp(torch.round(2.0 * scale / d4 - 0.5), 0, 15).to(torch.int64)
650
+ sgn_idx = _ksigns_encode(sgn) # [out, nblk, 8, 4] 0..127
651
+ aux32 = (ls << 28) | (sgn_idx << (7 * torch.arange(4, device=W.device)).view(1, 1, 1, 4))
652
+ aux32 = aux32[..., 0] | aux32[..., 1] | aux32[..., 2] | aux32[..., 3] # [out, nblk, 8]
653
+ # Per sub-group: 2 grid codes of 4 -> 2 bytes, interleaved (lo, hi).
654
+ # Layout of qs[0:64] per 8-group: [lo_il0, hi_il0, lo_il1, hi_il1, ...].
655
+ lo_b = (idx & 0xFF).view(out_f, nblk, 8, 4) # [.,.,8,4]
656
+ hi_b = ((idx >> 8) & 0xFF).view(out_f, nblk, 8, 4)
657
+ qs_idx = torch.stack([lo_b, hi_b], dim=-1).reshape(out_f, nblk, 64).to(torch.uint8)
658
+ aux_pairs = torch.stack([aux32 & 0xFFFF, aux32 >> 16], dim=-1) # [out, nblk, 8, 2]
659
+ return _cat_blocks([_pack_f16(d), qs_idx,
660
+ _pack_u16(aux_pairs.reshape(out_f, nblk, 16)).reshape(out_f, nblk, 32)],
661
+ out_f, nblk, 98)
662
+
663
+
664
+ def quantize_iq3_s(W: torch.Tensor) -> torch.Tensor:
665
+ """[out, in] fp32 -> IQ3_S bytes [out, 110*nblk].
666
+
667
+ qs[0:64] grid-index bytes (lo/hi interleaved), qh[8] high bits,
668
+ signs[32], scales[4] (nibble per group of a pair).
669
+ """
670
+ out_f, in_f = W.shape
671
+ nblk = (in_f + QK_K - 1) // QK_K
672
+ Wp = torch.zeros(out_f, nblk * QK_K, dtype=torch.float32, device=W.device)
673
+ Wp[:, :in_f] = W
674
+ x32 = Wp.view(out_f, nblk, QK_K)
675
+ sgn, xa = _pick_signs8(x32.view(-1, 8))
676
+ sgn = sgn.view(out_f, nblk, 8, 4, 8)
677
+ xa = xa.view(out_f, nblk, 8, 4, 8)
678
+ grid = iq_table("iq3s_grid").to(W.device) # [512, 4]
679
+ xa_4 = xa.view(out_f, nblk, 8, 4, 2, 4)
680
+ idx_lo, s_lo = _find_nearest(xa_4[..., 0, :].reshape(-1, 4), grid)
681
+ idx_hi, s_hi = _find_nearest(xa_4[..., 1, :].reshape(-1, 4), grid)
682
+ idx_lo = idx_lo.view(out_f, nblk, 8, 4).to(torch.int64)
683
+ idx_hi = idx_hi.view(out_f, nblk, 8, 4).to(torch.int64)
684
+ scale = torch.stack([s_lo, s_hi], -1).amax(-1).view(out_f, nblk, 8, 4)
685
+ # db per group (8 groups; scales[ib/2] nibble by ib%2, code = (unit/d-1)/2)
686
+ scale_g = scale.amax(dim=-1) # [out, nblk, 8]
687
+ sg_pairs = scale_g.view(out_f, nblk, 4, 2) # [out, nblk, 4, 2] (even, odd)
688
+ d = scale_g.amax(dim=-1) / 15.0 # [out, nblk]
689
+ d4 = d.unsqueeze(-1).unsqueeze(-1) # [out, nblk, 1, 1]
690
+ code_pair = torch.clamp(torch.round((sg_pairs / d4 - 1.0) * 0.5), 0, 15).to(torch.int64)
691
+ scales = (code_pair[..., 0] | (code_pair[..., 1] << 4)).to(torch.uint8) # [out, nblk, 4]
692
+ # qs bytes: interleave lo/hi codes per sub-group; qh carries high bits.
693
+ lo_code = (idx_lo & 0xFF).to(torch.uint8)
694
+ hi_code = (idx_hi & 0xFF).to(torch.uint8)
695
+ qs_idx = torch.stack([lo_code, hi_code], dim=-1).reshape(out_f, nblk, 64)
696
+ il2 = torch.arange(4, device=W.device)
697
+ qh_lo = (idx_lo >> 8) & 1
698
+ qh_hi = (idx_hi >> 8) & 1
699
+ qh = ((qh_lo << (2 * il2).view(1, 1, 1, 4))
700
+ | (qh_hi << (2 * il2 + 1).view(1, 1, 1, 4))).sum(-1) & 0xFF # [out, nblk, 8]
701
+ qh = qh.to(torch.uint8)
702
+ signs_b = (sgn * (1 << torch.arange(8, device=W.device))).sum(-1).to(torch.uint8) # [out,nblk,8,4]
703
+ return _cat_blocks([_pack_f16(d), qs_idx, qh,
704
+ signs_b.reshape(out_f, nblk, 32), scales],
705
+ out_f, nblk, 110)
706
+
707
+
708
+ def quantize_iq1_s(W: torch.Tensor) -> torch.Tensor:
709
+ """[out, in] fp32 -> IQ1_S bytes [out, 66*nblk].
710
+
711
+ d(f16) + qs[32] + qh[16]u16. grid = iq1s_grid [2048, 8] (signed, {-1,0,1}).
712
+ Per 32-group ib: dl = d*(2*((qh[ib]>>12)&7)+1); delta = +-IQ1S_DELTA
713
+ (sign from qh[ib]&0x8000); idx = qs[4*ib+il] | (((qh[ib]>>3*il)&7)<<8);
714
+ y[j] = dl * (grid[idx][j] + delta).
715
+ """
716
+ out_f, in_f = W.shape
717
+ nblk = (in_f + QK_K - 1) // QK_K
718
+ Wp = torch.zeros(out_f, nblk * QK_K, dtype=torch.float32, device=W.device)
719
+ Wp[:, :in_f] = W
720
+ x32 = Wp.view(out_f, nblk, QK_K)
721
+ grid = iq_table("iq1s_grid").to(W.device) # [2048, 8] int8 signed
722
+ x8 = x32.view(out_f * nblk * 32, 8)
723
+ # Search signed grid: dequant y = dl*(g+delta); for a candidate delta
724
+ # the effective target is x/dl - delta. Do a 2-pass: first find the best
725
+ # (idx, scale=dl) on the signed grid, then decide delta by the residual
726
+ # sign and re-pick if needed. Approx is fine at 1.5 bpw.
727
+ idx, scale = _find_nearest(x8, grid, signed=True)
728
+ idx = idx.view(out_f, nblk, 8, 4)
729
+ scale = scale.view(out_f, nblk, 8, 4) # dl per 8-group
730
+ # group multiplier: dl = d*(2*m+1), m 0..7
731
+ g_max = scale.amax(dim=-1).clamp(min=1e-8) # [out, nblk, 8]
732
+ m = torch.clamp(torch.round((g_max / g_max.amax(dim=-1).unsqueeze(-1) * 7 - 1) * 0.5), 0, 7).to(torch.int64)
733
+ d = (g_max.amax(dim=-1) / (2 * 7 + 1)).clamp(min=1e-8) # [out, nblk]
734
+ dl = d.unsqueeze(-1) * (2 * m + 1) # [out, nblk, 8] per super-group
735
+ id_dl = 1.0 / dl.unsqueeze(-1).unsqueeze(-1)
736
+ gv = grid[idx].to(torch.float32) # [out, nblk, 8, 4, 8]
737
+ rec = (dl.unsqueeze(-1).unsqueeze(-1) * gv) # [out, nblk, 8, 4, 8]
738
+ res = x32.view(out_f, nblk, 8, 4, 8) - rec
739
+ # delta: one sign per 32-group (aggregate residual over all 32 values)
740
+ delta = torch.where(res.mean(dim=(-1, -2)) < 0, -IQ1S_DELTA, IQ1S_DELTA) # [out, nblk, 8]
741
+ # adjust idx for the delta (approximate: re-search on x/dl - delta)
742
+ tgt = x32.view(out_f, nblk, 8, 4, 8) * id_dl - delta.unsqueeze(-1).unsqueeze(-1)
743
+ idx2, _ = _find_nearest(tgt.reshape(-1, 8), grid, signed=True)
744
+ idx = idx2.view(out_f, nblk, 8, 4)
745
+ # encode: qh[ib] = (sign delta << 15) | (m << 12) | (3 high bits of idx per il)
746
+ qh = (delta < 0).to(torch.int64) << 15 # [out, nblk, 8]
747
+ qh = qh | (m << 12) # [out, nblk, 8]
748
+ qs = (idx.to(torch.int64) & 0xFF) # low 8 bits per (ib, il)
749
+ hi3 = (idx.to(torch.int64) >> 8) & 7
750
+ qh = qh | (hi3 << (3 * torch.arange(4, device=W.device)).view(1, 1, 1, 4)).sum(-1)
751
+ # qh storage: 16 u16 per block (2 per group); decoder reads first of each pair.
752
+ qh16 = torch.zeros(out_f, nblk, 16, dtype=torch.int64, device=W.device)
753
+ qh16[..., 0::2] = qh
754
+ return _cat_blocks([_pack_f16(d), qs.to(torch.uint8).reshape(out_f, nblk, 32),
755
+ _pack_u16(qh16).reshape(out_f, nblk, 32)],
756
+ out_f, nblk, 66)
757
+
758
+
759
+ def quantize_iq4_xs(W: torch.Tensor) -> torch.Tensor:
760
+ """[out, in] fp32 -> IQ4_XS bytes [out, 136*nblk].
761
+
762
+ d(f16) + scales_h[2] + scales_l[4] + qs[128].
763
+ """
764
+ out_f, in_f = W.shape
765
+ nblk = (in_f + QK_K - 1) // QK_K
766
+ Wp = torch.zeros(out_f, nblk * QK_K, dtype=torch.float32, device=W.device)
767
+ Wp[:, :in_f] = W
768
+ bl = Wp.view(out_f, nblk, 8, 32)
769
+ amax = bl.abs().amax(dim=-1).clamp(min=1e-8) # [out, nblk, 8]
770
+ d = amax.amax(dim=-1) / 31.0 # [out, nblk]
771
+ kv = iq_table("kvalues_iq4nl").to(W.device) # [16]
772
+ id_d = 1.0 / d.unsqueeze(-1).unsqueeze(-1)
773
+ al = bl * id_d # [out, nblk, 8, 32]
774
+ codes = (al.unsqueeze(-1) - kv.view(1, 1, 1, 1, 16)).abs().argmin(-1) # [out,nblk,8,32]
775
+ # scales: 6-bit per group = (scales_l nibble) | (scales_h 2-bit << 4), -32 offset
776
+ ls = torch.clamp(torch.round(amax / d.unsqueeze(-1)), 0, 63).to(torch.int64) # [out,nblk,8]
777
+ ls = ls - 32
778
+ scales_l = (ls & 0xF).view(out_f, nblk, 4, 2)
779
+ scales_l = (scales_l[..., 0] | (scales_l[..., 1] << 4)).view(out_f, nblk, 4)
780
+ hi2 = (ls >> 4) & 3 # [out,nblk,8] 2-bit each
781
+ h0 = hi2[..., 0] | (hi2[..., 1] << 2) | (hi2[..., 2] << 4) | (hi2[..., 3] << 6)
782
+ h1 = hi2[..., 4] | (hi2[..., 5] << 2) | (hi2[..., 6] << 4) | (hi2[..., 7] << 6)
783
+ scales_h = torch.stack([h0, h1], dim=-1).view(out_f, nblk, 2).to(torch.int64)
784
+ packed = (codes[..., 0::2].to(torch.int64) | (codes[..., 1::2].to(torch.int64) << 4)).to(torch.uint8) # [out,nblk,8,16]
785
+ return _cat_blocks([_pack_f16(d), scales_h.to(torch.uint8),
786
+ scales_l.to(torch.uint8),
787
+ packed.reshape(out_f, nblk, 128)],
788
+ out_f, nblk, 136)
789
+
790
+
791
+ def quantize_iq4_nl(W: torch.Tensor) -> torch.Tensor:
792
+ """[out, in] fp32 -> IQ4_NL bytes [out, 18*nblk32].
793
+
794
+ Block = 32 values: d(f16) + qs[16]. y[j] = d*kvalues[qs&0xF],
795
+ y[j+16] = d*kvalues[qs>>4]. kvalues = [-127,-104,-83,-65,-49,-35,-22,
796
+ -10,1,13,25,38,53,69,89,113].
797
+ """
798
+ out_f, in_f = W.shape
799
+ nblk = (in_f + QK4_NL - 1) // QK4_NL
800
+ Wp = torch.zeros(out_f, nblk * QK4_NL, dtype=torch.float32, device=W.device)
801
+ Wp[:, :in_f] = W
802
+ bl = Wp.view(out_f, nblk, QK4_NL) # [out, nblk, 32]
803
+ kv = iq_table("kvalues_iq4nl").to(W.device) # [16] fp32
804
+ amax = bl.abs().amax(dim=-1).clamp(min=1e-8) # [out, nblk]
805
+ d = amax / kv.abs().max().clamp(min=1e-8) # [out, nblk]
806
+ id_d = 1.0 / d.unsqueeze(-1)
807
+ al = bl * id_d # [out, nblk, 32]
808
+ codes = (al.unsqueeze(-1) - kv.view(1, 1, 1, 16)).abs().argmin(-1) # [out,nblk,32]
809
+ packed = (codes[..., 0::2].to(torch.int64) | (codes[..., 1::2].to(torch.int64) << 4)).to(torch.uint8) # [out,nblk,16]
810
+ return _cat_blocks([_pack_f16(d), packed], out_f, nblk, 18)
811
+
812
+
813
+ def quantize_iq1_m(W: torch.Tensor) -> torch.Tensor:
814
+ """[out, in] fp32 -> IQ1_M bytes [out, 56*nblk].
815
+
816
+ qs[32] + qh[16] + scales[8 u16]. No fp16 d: scale is reassembled from the
817
+ top nibbles of the 8 scale u16 (see dequant_iq1_m_packed).
818
+ """
819
+ out_f, in_f = W.shape
820
+ nblk = (in_f + QK_K - 1) // QK_K
821
+ Wp = torch.zeros(out_f, nblk * QK_K, dtype=torch.float32, device=W.device)
822
+ Wp[:, :in_f] = W
823
+ x32 = Wp.view(out_f, nblk, QK_K)
824
+ grid = iq_table("iq1s_grid").to(W.device) # [2048, 8] signed {-1,0,1}
825
+ x8 = x32.view(out_f, nblk, 8, 4, 8)
826
+ # per 8-group: find best (idx, scale) on the signed grid
827
+ idx, scale = _find_nearest(x8.reshape(-1, 8), grid, signed=True)
828
+ idx = idx.view(out_f, nblk, 8, 4)
829
+ scale = scale.view(out_f, nblk, 8, 4)
830
+ # group dl (per 4 sub-groups of a 32-group... actually per 32-group pair):
831
+ # dl1 = d*(2*sc3+1), dl2 = d*(2*sc3+3) -> sc3 = 3-bit scale per 16-group.
832
+ # Approx: per 32-group, dl = max scale over its 4 sub-groups.
833
+ g_max = scale.amax(dim=-1).clamp(min=1e-8) # [out, nblk, 8] per 32-group
834
+ # per 32-group 2 codes: dl1 = max(scale of sub-groups 0,1), dl2 = max(2,3)
835
+ pair_scale = scale.view(out_f, nblk, 8, 2, 2).amax(dim=-1) # [out, nblk, 8, 2]
836
+ dmax = pair_scale.amax(dim=-1).amax(dim=-1).clamp(min=1e-8) # [out, nblk]
837
+ code2 = torch.clamp(torch.round((pair_scale / dmax.unsqueeze(-1).unsqueeze(-1) * 7 - 1) * 0.5), 0, 7).to(torch.int64) # [out,nblk,8,2]
838
+ dl = dmax.unsqueeze(-1).unsqueeze(-1) * (2 * code2 + 1) # [out, nblk, 8, 2]
839
+ # sc[p] u16 (p=0..3): 2 codes per 32-group, groups 2p (even, bits 0-5) and
840
+ # 2p+1 (odd, bits 6-11): [dl1, dl2] of each group.
841
+ c = code2.view(out_f, nblk, 4, 2, 2) # [out,nblk,4 u16,2 groups,2 dl]
842
+ gA1, gA2 = c[..., 0, 0], c[..., 0, 1]
843
+ gB1, gB2 = c[..., 1, 0], c[..., 1, 1]
844
+ sc16 = gA1 | (gA2 << 3) | (gB1 << 6) | (gB2 << 9) # [out, nblk, 4]
845
+ # fp16 d in the top nibbles across the 4 u16:
846
+ # d16 = (sc0>>12) | ((sc1>>8)&0xF0) | ((sc2>>4)&0xF00) | (sc3&0xF000)
847
+ d16 = dmax.to(torch.float16).view(torch.int16).to(torch.int64) & 0xFFFF # [out,nblk]
848
+ d16e = d16.unsqueeze(-1) # [out, nblk, 1]
849
+ sc16 = sc16 | (((d16e >> 12) & 0xF) << 12)
850
+ sc16 = sc16 | ((((d16e >> 8) & 0xF) << 8) & 0x0F00)
851
+ sc16 = sc16 | ((((d16e >> 4) & 0xF) << 4) & 0x00F0)
852
+ sc16 = sc16 | (d16e & 0x000F)
853
+ # delta per group (from qh 0x08 / 0x80 bits) + idx high bits in qh
854
+ delta = torch.where(g_max / dmax.unsqueeze(-1) < 0.5, -IQ1M_DELTA, IQ1M_DELTA) # placeholder
855
+ delta = torch.full_like(g_max, IQ1M_DELTA) # sign from residual below
856
+ # delta: reconstruct with dl per (group, pair): dl1 for il 0..1, dl2 for 2..3
857
+ dl_pair = dl.view(out_f, nblk, 8, 2, 1) # [out,nblk,8,2 dl,1]
858
+ dl_full = dl_pair.expand(-1, -1, -1, -1, 2).reshape(out_f, nblk, 8, 4) # per sub-group
859
+ gv = grid[idx].to(torch.float32) # [out,nblk,8,4,8]
860
+ res = x8 - dl_full.unsqueeze(-1) * gv
861
+ delta = torch.where(res.mean(dim=(-1, -2)) < 0, -IQ1M_DELTA, IQ1M_DELTA) # [out,nblk,8]
862
+ # qh: 16 bytes per block (2 per 32-group). Per 32-group:
863
+ # qh[0] bits: idx0 hi3<<8, idx1 hi3<<4, delta0<<3, delta1<<7 (via 0x08/0x80)
864
+ # qh[1] bits: idx2 hi3<<8, idx3 hi3<<4, delta2<<3, delta3<<7
865
+ hi3 = (idx.to(torch.int64) >> 8) & 7 # [out,nblk,8,4]
866
+ qh2 = torch.zeros(out_f, nblk, 8, 2, dtype=torch.int64, device=W.device)
867
+ qh2[..., 0] |= (hi3[..., 0] << 8) | (hi3[..., 1] << 4)
868
+ qh2[..., 1] |= (hi3[..., 2] << 8) | (hi3[..., 3] << 4)
869
+ dlt = (delta < 0).to(torch.int64) # [out,nblk,8]
870
+ qh2[..., 0] |= (dlt << 3) & 0x08
871
+ qh2[..., 0] |= (dlt << 7) & 0x80
872
+ qh2[..., 1] |= (dlt << 3) & 0x08
873
+ qh2[..., 1] |= (dlt << 7) & 0x80
874
+ qs = (idx.to(torch.int64) & 0xFF)
875
+ return _cat_blocks([qs.to(torch.uint8).reshape(out_f, nblk, 32),
876
+ qh2.to(torch.uint8).reshape(out_f, nblk, 16),
877
+ _pack_u16(sc16).reshape(out_f, nblk, 8)],
878
+ out_f, nblk, 56)