Coverage Report

Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/realizar/src/quantize/dequant.rs
Line
Count
Source
1
//! Dequantization functions for GGUF quantization formats (PMAT-802)
2
//!
3
//! Extracted from quantize/mod.rs - Basic dequantization for Q4_0, Q8_0, F16,
4
//! Q4_1, Q5_0, Q5_1, Q4_K, Q5_K, Q6_K, Q2_K formats.
5
//!
6
//! ## Contents
7
//! - `dequantize_q4_0` - 4-bit quantization (block size 32)
8
//! - `dequantize_q8_0` - 8-bit quantization (block size 32)
9
//! - `dequantize_f16` - Half-precision to f32
10
//! - `dequantize_q4_1` - 4-bit with scale and min
11
//! - `dequantize_q5_0` - 5-bit quantization
12
//! - `dequantize_q5_1` - 5-bit with scale and min
13
//! - `dequantize_q4_k` - K-quantization 4-bit (super-block 256)
14
//! - `dequantize_q5_k` - K-quantization 5-bit (super-block 256)
15
//! - `dequantize_q6_k` - K-quantization 6-bit (super-block 256)
16
//! - `dequantize_q2_k` - K-quantization 2-bit (super-block 256)
17
18
use crate::error::{RealizarError, Result};
19
use crate::quantize::{BLOCK_SIZE, QK_K};
20
21
// Re-use helpers from simd module
22
use super::simd::extract_scale_min;
23
24
/// Dequantize `Q4_0` format weights
25
///
26
/// # Arguments
27
///
28
/// * `data` - Raw `Q4_0` quantized data (blocks of scale + 16 bytes)
29
///
30
/// # Returns
31
///
32
/// Dequantized float32 values
33
///
34
/// # Errors
35
///
36
/// Returns error if data length is not a multiple of block size
37
///
38
/// # Examples
39
///
40
/// ```rust,ignore
41
/// let quantized = load_q4_0_weights();
42
/// let weights = dequantize_q4_0(&quantized)?;
43
/// ```
44
16
pub fn dequantize_q4_0(data: &[u8]) -> Result<Vec<f32>> {
45
    // Q4_0 block: 2 bytes (f16 scale) + 16 bytes (quants) = 18 bytes
46
    // GGML spec: typedef struct { ggml_half d; uint8_t qs[QK4_0/2]; } block_q4_0;
47
    const BLOCK_BYTES: usize = 2 + 16;
48
49
16
    if !data.len().is_multiple_of(BLOCK_BYTES) {
50
2
        return Err(RealizarError::InvalidShape {
51
2
            reason: format!(
52
2
                "Q4_0 data length {} is not a multiple of block size {}",
53
2
                data.len(),
54
2
                BLOCK_BYTES
55
2
            ),
56
2
        });
57
14
    }
58
59
14
    let num_blocks = data.len() / BLOCK_BYTES;
60
14
    let mut result = vec![0.0f32; num_blocks * BLOCK_SIZE];
61
62
3.59k
    for block_idx in 0..
num_blocks14
{
63
3.59k
        let block_start = block_idx * BLOCK_BYTES;
64
3.59k
        let out_start = block_idx * BLOCK_SIZE;
65
66
        // Read scale (f16, per GGML spec)
67
3.59k
        let scale_bytes = &data[block_start..block_start + 2];
68
3.59k
        let scale = half::f16::from_le_bytes([scale_bytes[0], scale_bytes[1]]).to_f32();
69
70
        // Read quantized values (16 bytes)
71
3.59k
        let quants_start = block_start + 2;
72
3.59k
        let quants = &data[quants_start..quants_start + 16];
73
74
        // Dequantize following candle's layout:
75
        // - Positions 0-15: low nibbles of bytes 0-15
76
        // - Positions 16-31: high nibbles of bytes 0-15
77
57.5k
        for (j, &byte) in 
quants3.59k
.
iter3.59k
().
enumerate3.59k
() {
78
57.5k
            // Low 4 bits go to position j
79
57.5k
            #[allow(clippy::cast_possible_wrap)]
80
57.5k
            let low = (byte & 0x0F) as i16 - 8;
81
57.5k
            result[out_start + j] = scale * (low as f32);
82
57.5k
83
57.5k
            // High 4 bits go to position j + 16
84
57.5k
            #[allow(clippy::cast_possible_wrap)]
85
57.5k
            let high = (byte >> 4) as i16 - 8;
86
57.5k
            result[out_start + j + 16] = scale * (high as f32);
87
57.5k
        }
88
    }
89
90
14
    Ok(result)
91
16
}
92
93
/// Dequantize `Q8_0` format weights
94
///
95
/// # Arguments
96
///
97
/// * `data` - Raw `Q8_0` quantized data (blocks of scale + 32 int8 values)
98
///
99
/// # Returns
100
///
101
/// Dequantized float32 values
102
///
103
/// # Errors
104
///
105
/// Returns error if data length is not a multiple of block size
106
17
pub fn dequantize_q8_0(data: &[u8]) -> Result<Vec<f32>> {
107
    // Q8_0 block: 2 bytes (f16 scale) + 32 bytes (int8 quants) = 34 bytes
108
    // Note: GGML spec uses f16 for scale, not f32!
109
    const BLOCK_BYTES: usize = 2 + 32;
110
111
17
    if !data.len().is_multiple_of(BLOCK_BYTES) {
112
2
        return Err(RealizarError::InvalidShape {
113
2
            reason: format!(
114
2
                "Q8_0 data length {} is not a multiple of block size {}",
115
2
                data.len(),
116
2
                BLOCK_BYTES
117
2
            ),
118
2
        });
119
15
    }
120
121
15
    let num_blocks = data.len() / BLOCK_BYTES;
122
15
    let mut result = Vec::with_capacity(num_blocks * BLOCK_SIZE);
123
124
15
    for block_idx in 0..num_blocks {
125
15
        let block_start = block_idx * BLOCK_BYTES;
126
127
        // Read scale (f16 -> f32)
128
15
        let scale_bits = u16::from_le_bytes([data[block_start], data[block_start + 1]]);
129
15
        let scale = f16_to_f32(scale_bits);
130
131
        // Read quantized values (32 int8 values)
132
15
        let quants_start = block_start + 2;
133
15
        let quants = &data[quants_start..quants_start + 32];
134
135
        // Dequantize
136
495
        for &
byte480
in quants {
137
480
            let value = i8::from_le_bytes([byte]);
138
480
            result.push(scale * f32::from(value));
139
480
        }
140
    }
141
142
15
    Ok(result)
143
17
}
144
145
/// Convert IEEE 754 half-precision (f16) to single-precision (f32)
146
///
147
/// Handles normal values, subnormals, infinities, and NaN.
148
#[inline]
149
65.1k
pub fn f16_to_f32(h: u16) -> f32 {
150
65.1k
    let sign = (h >> 15) & 1;
151
65.1k
    let exp = (h >> 10) & 0x1F;
152
65.1k
    let mantissa = h & 0x3FF;
153
154
65.1k
    if exp == 0 {
155
        // Subnormal or zero
156
54.8k
        if mantissa == 0 {
157
            // Zero (preserve sign)
158
54.8k
            if sign == 1 {
159
4
                -0.0
160
            } else {
161
54.8k
                0.0
162
            }
163
        } else {
164
            // Subnormal: (mantissa / 1024) * 2^-14
165
6
            let value = (mantissa as f32 / 1024.0) * (2.0_f32).powi(-14);
166
6
            if sign == 1 {
167
2
                -value
168
            } else {
169
4
                value
170
            }
171
        }
172
10.3k
    } else if exp == 31 {
173
        // Infinity or NaN
174
13
        if mantissa == 0 {
175
10
            if sign == 1 {
176
5
                f32::NEG_INFINITY
177
            } else {
178
5
                f32::INFINITY
179
            }
180
        } else {
181
3
            f32::NAN
182
        }
183
    } else {
184
        // Normal value: (1 + mantissa/1024) * 2^(exp-15)
185
10.3k
        let value = (1.0 + mantissa as f32 / 1024.0) * (2.0_f32).powi(exp as i32 - 15);
186
10.3k
        if sign == 1 {
187
8
            -value
188
        } else {
189
10.3k
            value
190
        }
191
    }
192
65.1k
}
193
194
/// Dequantize `F16` format weights to `F32`
195
///
196
/// # Arguments
197
///
198
/// * `data` - Raw F16 data (2 bytes per value)
199
///
200
/// # Returns
201
///
202
/// Dequantized float32 values
203
///
204
/// # Errors
205
///
206
/// Returns error if data length is not a multiple of 2 bytes
207
23
pub fn dequantize_f16(data: &[u8]) -> Result<Vec<f32>> {
208
23
    if !data.len().is_multiple_of(2) {
209
8
        return Err(RealizarError::InvalidShape {
210
8
            reason: format!(
211
8
                "F16 data length {} is not a multiple of 2 bytes",
212
8
                data.len()
213
8
            ),
214
8
        });
215
15
    }
216
217
15
    let num_values = data.len() / 2;
218
15
    let mut result = Vec::with_capacity(num_values);
219
220
22
    for chunk in 
data15
.
chunks_exact15
(2) {
221
22
        let h = u16::from_le_bytes([chunk[0], chunk[1]]);
222
22
        result.push(f16_to_f32(h));
223
22
    }
224
225
15
    Ok(result)
226
23
}
227
228
/// Dequantize `Q4_1` format weights
229
///
230
/// Q4_1 format: 2 bytes (f16 scale) + 2 bytes (f16 min) + 16 bytes (quants) = 20 bytes
231
/// GGUF/candle layout:
232
/// - Positions 0-15: low nibbles of bytes 0-15
233
/// - Positions 16-31: high nibbles of bytes 0-15
234
29
pub fn dequantize_q4_1(data: &[u8]) -> Result<Vec<f32>> {
235
    const BLOCK_BYTES: usize = 20;
236
237
29
    if !data.len().is_multiple_of(BLOCK_BYTES) {
238
6
        return Err(RealizarError::InvalidShape {
239
6
            reason: format!(
240
6
                "Q4_1 data length {} is not a multiple of block size {}",
241
6
                data.len(),
242
6
                BLOCK_BYTES
243
6
            ),
244
6
        });
245
23
    }
246
247
23
    let num_blocks = data.len() / BLOCK_BYTES;
248
    // Pre-allocate with correct size for candle layout
249
23
    let mut result = vec![0.0f32; num_blocks * BLOCK_SIZE];
250
251
29.7k
    for block_idx in 0..
num_blocks23
{
252
29.7k
        let block_start = block_idx * BLOCK_BYTES;
253
29.7k
        let out_start = block_idx * BLOCK_SIZE;
254
255
29.7k
        let d_bytes = &data[block_start..block_start + 2];
256
29.7k
        let d = f16_to_f32(u16::from_le_bytes([d_bytes[0], d_bytes[1]]));
257
258
29.7k
        let min_bytes = &data[block_start + 2..block_start + 4];
259
29.7k
        let min = f16_to_f32(u16::from_le_bytes([min_bytes[0], min_bytes[1]]));
260
261
29.7k
        let quants = &data[block_start + 4..block_start + 20];
262
263
        // Use candle layout (same as Q4_0):
264
        // - Low nibbles (byte & 0xF) at positions 0-15
265
        // - High nibbles (byte >> 4) at positions 16-31
266
475k
        for (j, &byte) in 
quants29.7k
.
iter29.7k
().
enumerate29.7k
() {
267
475k
            // Low 4 bits go to position j (0-15)
268
475k
            let low = byte & 0x0F;
269
475k
            result[out_start + j] = d * f32::from(low) + min;
270
475k
271
475k
            // High 4 bits go to position j + 16 (16-31)
272
475k
            let high = (byte >> 4) & 0x0F;
273
475k
            result[out_start + j + 16] = d * f32::from(high) + min;
274
475k
        }
275
    }
276
277
23
    Ok(result)
278
29
}
279
280
/// Dequantize `Q5_0` format weights
281
///
282
/// Q5_0 format: 2 bytes (f16 scale) + 4 bytes (high bits) + 16 bytes (quants) = 22 bytes
283
/// GGUF/candle layout:
284
/// - Positions 0-15: low nibbles + high bits from qh
285
/// - Positions 16-31: high nibbles + high bits from qh
286
26
pub fn dequantize_q5_0(data: &[u8]) -> Result<Vec<f32>> {
287
    const BLOCK_BYTES: usize = 22;
288
289
26
    if !data.len().is_multiple_of(BLOCK_BYTES) {
290
6
        return Err(RealizarError::InvalidShape {
291
6
            reason: format!(
292
6
                "Q5_0 data length {} is not a multiple of block size {}",
293
6
                data.len(),
294
6
                BLOCK_BYTES
295
6
            ),
296
6
        });
297
20
    }
298
299
20
    let num_blocks = data.len() / BLOCK_BYTES;
300
    // Pre-allocate with correct size for candle layout
301
20
    let mut result = vec![0.0f32; num_blocks * BLOCK_SIZE];
302
303
4.62k
    for block_idx in 0..
num_blocks20
{
304
4.62k
        let block_start = block_idx * BLOCK_BYTES;
305
4.62k
        let out_start = block_idx * BLOCK_SIZE;
306
307
4.62k
        let d_bytes = &data[block_start..block_start + 2];
308
4.62k
        let d = f16_to_f32(u16::from_le_bytes([d_bytes[0], d_bytes[1]]));
309
310
4.62k
        let qh = u32::from_le_bytes([
311
4.62k
            data[block_start + 2],
312
4.62k
            data[block_start + 3],
313
4.62k
            data[block_start + 4],
314
4.62k
            data[block_start + 5],
315
4.62k
        ]);
316
317
4.62k
        let qs = &data[block_start + 6..block_start + 22];
318
319
        // Use candle layout:
320
        // - Low nibbles (byte & 0xF) at positions 0-15
321
        // - High nibbles (byte >> 4) at positions 16-31
322
73.9k
        for (i, &byte) in 
qs4.62k
.
iter4.62k
().
enumerate4.62k
() {
323
73.9k
            // Low 4 bits + 5th bit go to position i (0-15)
324
73.9k
            let low_q = byte & 0x0F;
325
73.9k
            let high_bit_low = ((qh >> i) & 1) as u8;
326
73.9k
            let q_low = low_q | (high_bit_low << 4);
327
73.9k
            #[allow(clippy::cast_possible_wrap)]
328
73.9k
            let value_low = q_low as i8 - 16;
329
73.9k
            result[out_start + i] = d * f32::from(value_low);
330
73.9k
331
73.9k
            // High 4 bits + 5th bit go to position i + 16 (16-31)
332
73.9k
            let high_q = (byte >> 4) & 0x0F;
333
73.9k
            let high_bit_high = ((qh >> (i + 16)) & 1) as u8;
334
73.9k
            let q_high = high_q | (high_bit_high << 4);
335
73.9k
            #[allow(clippy::cast_possible_wrap)]
336
73.9k
            let value_high = q_high as i8 - 16;
337
73.9k
            result[out_start + i + 16] = d * f32::from(value_high);
338
73.9k
        }
339
    }
340
341
20
    Ok(result)
342
26
}
343
344
/// Dequantize `Q5_1` format weights
345
///
346
/// Q5_1 format: 2 bytes (f16 scale) + 2 bytes (f16 min) + 4 bytes (high bits) + 16 bytes (quants) = 24 bytes
347
/// GGUF/candle layout:
348
/// - Positions 0-15: low nibbles + high bits from qh
349
/// - Positions 16-31: high nibbles + high bits from qh
350
24
pub fn dequantize_q5_1(data: &[u8]) -> Result<Vec<f32>> {
351
    const BLOCK_BYTES: usize = 24;
352
353
24
    if !data.len().is_multiple_of(BLOCK_BYTES) {
354
6
        return Err(RealizarError::InvalidShape {
355
6
            reason: format!(
356
6
                "Q5_1 data length {} is not a multiple of block size {}",
357
6
                data.len(),
358
6
                BLOCK_BYTES
359
6
            ),
360
6
        });
361
18
    }
362
363
18
    let num_blocks = data.len() / BLOCK_BYTES;
364
    // Pre-allocate with correct size for candle layout
365
18
    let mut result = vec![0.0f32; num_blocks * BLOCK_SIZE];
366
367
18
    for 
block_idx17
in 0..num_blocks {
368
17
        let block_start = block_idx * BLOCK_BYTES;
369
17
        let out_start = block_idx * BLOCK_SIZE;
370
371
17
        let d_bytes = &data[block_start..block_start + 2];
372
17
        let d = f16_to_f32(u16::from_le_bytes([d_bytes[0], d_bytes[1]]));
373
374
17
        let min_bytes = &data[block_start + 2..block_start + 4];
375
17
        let min = f16_to_f32(u16::from_le_bytes([min_bytes[0], min_bytes[1]]));
376
377
17
        let qh = u32::from_le_bytes([
378
17
            data[block_start + 4],
379
17
            data[block_start + 5],
380
17
            data[block_start + 6],
381
17
            data[block_start + 7],
382
17
        ]);
383
384
17
        let qs = &data[block_start + 8..block_start + 24];
385
386
        // Use candle layout:
387
        // - Low nibbles (byte & 0xF) at positions 0-15
388
        // - High nibbles (byte >> 4) at positions 16-31
389
272
        for (i, &byte) in 
qs17
.
iter17
().
enumerate17
() {
390
272
            // Low 4 bits + 5th bit go to position i (0-15)
391
272
            let low_q = byte & 0x0F;
392
272
            let high_bit_low = ((qh >> i) & 1) as u8;
393
272
            let q_low = low_q | (high_bit_low << 4);
394
272
            result[out_start + i] = d * f32::from(q_low) + min;
395
272
396
272
            // High 4 bits + 5th bit go to position i + 16 (16-31)
397
272
            let high_q = (byte >> 4) & 0x0F;
398
272
            let high_bit_high = ((qh >> (i + 16)) & 1) as u8;
399
272
            let q_high = high_q | (high_bit_high << 4);
400
272
            result[out_start + i + 16] = d * f32::from(q_high) + min;
401
272
        }
402
    }
403
404
18
    Ok(result)
405
24
}
406
407
/// Dequantize `Q4_K` format weights
408
259
pub fn dequantize_q4_k(data: &[u8]) -> Result<Vec<f32>> {
409
    const SUPER_BLOCK_BYTES: usize = 144;
410
411
259
    if !data.len().is_multiple_of(SUPER_BLOCK_BYTES) {
412
2
        return Err(RealizarError::InvalidShape {
413
2
            reason: format!(
414
2
                "Q4_K data length {} is not a multiple of super-block size {}",
415
2
                data.len(),
416
2
                SUPER_BLOCK_BYTES
417
2
            ),
418
2
        });
419
257
    }
420
421
257
    let num_super_blocks = data.len() / SUPER_BLOCK_BYTES;
422
257
    let mut result = vec![0.0f32; num_super_blocks * QK_K];
423
424
736
    for sb_idx in 0..
num_super_blocks257
{
425
736
        let sb_start = sb_idx * SUPER_BLOCK_BYTES;
426
736
        let out_start = sb_idx * QK_K;
427
428
736
        let d = read_f16(&data[sb_start..sb_start + 2]);
429
736
        let dmin = read_f16(&data[sb_start + 2..sb_start + 4]);
430
431
736
        let mut scales = [0u8; 12];
432
736
        scales.copy_from_slice(&data[sb_start + 4..sb_start + 16]);
433
434
736
        let qs_start = sb_start + 16;
435
736
        let qs = &data[qs_start..qs_start + 128];
436
437
736
        let mut ys_index = out_start;
438
439
2.94k
        for j in 
(0..QK_K)736
.
step_by736
(64) {
440
2.94k
            let q = &qs[j / 2..j / 2 + 32];
441
442
2.94k
            let is = j / 32;
443
2.94k
            let (sc1, m1) = extract_scale_min(&scales, is);
444
2.94k
            let d1 = d * sc1;
445
2.94k
            let dm1 = dmin * m1;
446
447
2.94k
            let (sc2, m2) = extract_scale_min(&scales, is + 1);
448
2.94k
            let d2 = d * sc2;
449
2.94k
            let dm2 = dmin * m2;
450
451
97.1k
            for &
byte94.2k
in q {
452
94.2k
                result[ys_index] = d1 * (byte & 0xF) as f32 - dm1;
453
94.2k
                ys_index += 1;
454
94.2k
            }
455
456
97.1k
            for &
byte94.2k
in q {
457
94.2k
                result[ys_index] = d2 * (byte >> 4) as f32 - dm2;
458
94.2k
                ys_index += 1;
459
94.2k
            }
460
        }
461
    }
462
463
257
    Ok(result)
464
259
}
465
466
/// Dequantize `Q5_K` format weights
467
17
pub fn dequantize_q5_k(data: &[u8]) -> Result<Vec<f32>> {
468
    const SUPER_BLOCK_BYTES: usize = 176;
469
470
17
    if !data.len().is_multiple_of(SUPER_BLOCK_BYTES) {
471
3
        return Err(RealizarError::InvalidShape {
472
3
            reason: format!(
473
3
                "Q5_K data length {} is not a multiple of super-block size {}",
474
3
                data.len(),
475
3
                SUPER_BLOCK_BYTES
476
3
            ),
477
3
        });
478
14
    }
479
480
14
    let num_super_blocks = data.len() / SUPER_BLOCK_BYTES;
481
14
    let mut result = Vec::with_capacity(num_super_blocks * QK_K);
482
483
15
    for sb_idx in 0..
num_super_blocks14
{
484
15
        let sb_start = sb_idx * SUPER_BLOCK_BYTES;
485
486
15
        let d = read_f16(&data[sb_start..sb_start + 2]);
487
15
        let dmin = read_f16(&data[sb_start + 2..sb_start + 4]);
488
489
15
        let mut scales = [0u8; 12];
490
15
        scales.copy_from_slice(&data[sb_start + 4..sb_start + 16]);
491
492
15
        let qh_start = sb_start + 16;
493
15
        let qh = &data[qh_start..qh_start + 32];
494
495
15
        let qs_low_start = sb_start + 48;
496
15
        let qs = &data[qs_low_start..qs_low_start + 128];
497
498
135
        for 
block_idx120
in 0..8 {
499
120
            let (scale, min) = extract_scale_min(&scales, block_idx);
500
501
120
            let block_start = block_idx * 16;
502
120
            let qh_block_start = block_idx * 4;
503
504
2.04k
            for 
byte_idx1.92k
in 0..16 {
505
1.92k
                let qs_byte = qs[block_start + byte_idx];
506
1.92k
507
1.92k
                let high_bits_byte = qh[qh_block_start + byte_idx / 4];
508
1.92k
                let bit_offset = (byte_idx % 4) * 2;
509
1.92k
510
1.92k
                let q_low_4bit = qs_byte & 0x0F;
511
1.92k
                let q_low_high_bit = (high_bits_byte >> bit_offset) & 0x01;
512
1.92k
                #[allow(clippy::cast_possible_wrap)]
513
1.92k
                let q_low = ((q_low_high_bit << 4) | q_low_4bit) as i8;
514
1.92k
                let value_low = d * scale * f32::from(q_low) - dmin * min;
515
1.92k
                result.push(value_low);
516
1.92k
517
1.92k
                let q_high_4bit = (qs_byte >> 4) & 0x0F;
518
1.92k
                let q_high_high_bit = (high_bits_byte >> (bit_offset + 1)) & 0x01;
519
1.92k
                #[allow(clippy::cast_possible_wrap)]
520
1.92k
                let q_high = ((q_high_high_bit << 4) | q_high_4bit) as i8;
521
1.92k
                let value_high = d * scale * f32::from(q_high) - dmin * min;
522
1.92k
                result.push(value_high);
523
1.92k
            }
524
        }
525
    }
526
527
14
    Ok(result)
528
17
}
529
530
/// Dequantize `Q6_K` format weights
531
20
pub fn dequantize_q6_k(data: &[u8]) -> Result<Vec<f32>> {
532
    const SUPER_BLOCK_BYTES: usize = 210;
533
534
20
    if !data.len().is_multiple_of(SUPER_BLOCK_BYTES) {
535
3
        return Err(RealizarError::InvalidShape {
536
3
            reason: format!(
537
3
                "Q6_K data length {} is not a multiple of super-block size {}",
538
3
                data.len(),
539
3
                SUPER_BLOCK_BYTES
540
3
            ),
541
3
        });
542
17
    }
543
544
17
    let num_super_blocks = data.len() / SUPER_BLOCK_BYTES;
545
17
    let mut result = vec![0.0f32; num_super_blocks * QK_K];
546
547
22
    for sb_idx in 0..
num_super_blocks17
{
548
22
        let sb_start = sb_idx * SUPER_BLOCK_BYTES;
549
22
        let out_start = sb_idx * QK_K;
550
551
22
        let ql = &data[sb_start..sb_start + 128];
552
22
        let qh = &data[sb_start + 128..sb_start + 192];
553
554
22
        let mut scales = [0i8; 16];
555
352
        for (i, scale) in 
scales22
.
iter_mut22
().
enumerate22
() {
556
            #[allow(clippy::cast_possible_wrap)]
557
352
            {
558
352
                *scale = data[sb_start + 192 + i] as i8;
559
352
            }
560
        }
561
562
22
        let d = read_f16(&data[sb_start + 208..sb_start + 210]);
563
564
44
        for n in 
(0..QK_K)22
.
step_by22
(128) {
565
44
            let idx = n / 128;
566
44
            let sc = &scales[8 * idx..];
567
44
            let ql_slice = &ql[64 * idx..];
568
44
            let qh_slice = &qh[32 * idx..];
569
570
1.45k
            for 
l1.40k
in 0..32 {
571
1.40k
                let is = l / 16;
572
1.40k
573
1.40k
                let q1 = ((ql_slice[l] & 0xF) | ((qh_slice[l] & 3) << 4)) as i32 - 32;
574
1.40k
                let q2 = ((ql_slice[l + 32] & 0xF) | (((qh_slice[l] >> 2) & 3) << 4)) as i32 - 32;
575
1.40k
                let q3 = ((ql_slice[l] >> 4) | (((qh_slice[l] >> 4) & 3) << 4)) as i32 - 32;
576
1.40k
                let q4 = ((ql_slice[l + 32] >> 4) | (((qh_slice[l] >> 6) & 3) << 4)) as i32 - 32;
577
1.40k
578
1.40k
                result[out_start + n + l] = d * (sc[is] as f32) * (q1 as f32);
579
1.40k
                result[out_start + n + l + 32] = d * (sc[is + 2] as f32) * (q2 as f32);
580
1.40k
                result[out_start + n + l + 64] = d * (sc[is + 4] as f32) * (q3 as f32);
581
1.40k
                result[out_start + n + l + 96] = d * (sc[is + 6] as f32) * (q4 as f32);
582
1.40k
            }
583
        }
584
    }
585
586
17
    Ok(result)
587
20
}
588
589
/// Dequantize `Q2_K` format weights
590
2
pub fn dequantize_q2_k(data: &[u8]) -> Result<Vec<f32>> {
591
    const SUPER_BLOCK_BYTES: usize = 84;
592
593
2
    if !data.len().is_multiple_of(SUPER_BLOCK_BYTES) {
594
0
        return Err(RealizarError::InvalidShape {
595
0
            reason: format!(
596
0
                "Q2_K data length {} is not a multiple of super-block size {}",
597
0
                data.len(),
598
0
                SUPER_BLOCK_BYTES
599
0
            ),
600
0
        });
601
2
    }
602
603
2
    let num_super_blocks = data.len() / SUPER_BLOCK_BYTES;
604
2
    let mut result = vec![0.0f32; num_super_blocks * QK_K];
605
606
2
    for sb_idx in 0..num_super_blocks {
607
2
        let sb_start = sb_idx * SUPER_BLOCK_BYTES;
608
2
        let out_start = sb_idx * QK_K;
609
610
2
        let scales_data = &data[sb_start..sb_start + 16];
611
2
        let qs = &data[sb_start + 16..sb_start + 80];
612
613
2
        let d = read_f16(&data[sb_start + 80..sb_start + 82]);
614
2
        let dmin = read_f16(&data[sb_start + 82..sb_start + 84]);
615
616
34
        for 
j32
in 0..16 {
617
32
            let sc = (scales_data[j] & 0x0F) as f32;
618
32
            let m = (scales_data[j] >> 4) as f32;
619
620
32
            let d_sc = d * sc;
621
32
            let dm = dmin * m;
622
623
32
            let qs_offset = j * 4;
624
625
160
            for 
k128
in 0..4 {
626
128
                let q_byte = qs[qs_offset + k];
627
128
                let q0 = (q_byte & 0x03) as f32;
628
128
                let q1 = ((q_byte >> 2) & 0x03) as f32;
629
128
                let q2 = ((q_byte >> 4) & 0x03) as f32;
630
128
                let q3 = ((q_byte >> 6) & 0x03) as f32;
631
128
632
128
                let base_idx = out_start + j * 16 + k * 4;
633
128
                result[base_idx] = d_sc * q0 - dm;
634
128
                result[base_idx + 1] = d_sc * q1 - dm;
635
128
                result[base_idx + 2] = d_sc * q2 - dm;
636
128
                result[base_idx + 3] = d_sc * q3 - dm;
637
128
            }
638
        }
639
    }
640
641
2
    Ok(result)
642
2
}
643
644
/// Helper: Read f16 from bytes and convert to f32
645
#[inline]
646
2.52M
pub(crate) fn read_f16(bytes: &[u8]) -> f32 {
647
2.52M
    let bits = u16::from_le_bytes([bytes[0], bytes[1]]);
648
2.52M
    half::f16::from_bits(bits).to_f32()
649
2.52M
}