File size: 4,015 Bytes
d2ea1d1
be99550
d2ea1d1
be99550
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d2ea1d1
be99550
 
 
 
d2ea1d1
 
be99550
 
d2ea1d1
be99550
d2ea1d1
 
 
be99550
d2ea1d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
be99550
 
 
d2ea1d1
 
be99550
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// ๐Ÿ”ฌ [2-Bit ์ •์ˆ˜ ํ…์„œ ์™„์ „ ์–‘์žํ™” ๋ฐ Rayon + SIMD ๋ณ‘๋ ฌ ์ดˆ๊ณ ์† GEMV ์—”์ง„] (src/tensor_quantizer.rs)
// ๋Œ€๊ทœ๋ชจ ์‹ ๊ฒฝ๋ง(Transformer)์˜ FP32/FP16 ๊ฐ€์ค‘์น˜ ํ…์„œ๋ฅผ 2-Bit๋กœ ํŒจํ‚นํ•˜๊ณ  ๋น„ํŠธ ์—ฐ์‚ฐ์œผ๋กœ ์ถ”๋ก ํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ๋Ÿฌ์ŠคํŠธ ์—”์ง„
use rayon::prelude::*;

pub struct QuantizedTensor2Bit {
    pub rows: usize,
    pub cols: usize,
    pub packed_data: Vec<u32>, // ์—ด(col) ๋ฐฉํ–ฅ์œผ๋กœ 16๊ฐœ ๊ฐ€์ค‘์น˜๋งˆ๋‹ค 1๊ฐœ uint32 ํŒจํ‚น
    pub scales: Vec<f32>,       // ํ–‰(row)๋ณ„ ์—ญ์–‘์žํ™” ์Šค์ผ€์ผ ํŒฉํ„ฐ
    pub original_bytes: usize,
    pub compressed_bytes: usize,
}

impl QuantizedTensor2Bit {
    /// FP32 ์—ฐ์† ํ…์„œ๋ฅผ 2-Bit 8๋Œ€ ์œ„์ƒ์œผ๋กœ ์–‘์žํ™” ๋ฐ ๋น„ํŠธํŒจํ‚น
    pub fn quantize(raw_fp32: &[f32], rows: usize, cols: usize) -> Self {
        assert_eq!(raw_fp32.len(), rows * cols, "ํ…์„œ ํฌ๊ธฐ ๋ถˆ์ผ์น˜");
        let packed_cols = (cols + 15) / 16;
        let mut packed_data = vec![0u32; rows * packed_cols];
        let mut scales = vec![1.0f32; rows];

        for r in 0..rows {
            let row_slice = &raw_fp32[r * cols..(r + 1) * cols];
            let max_abs = row_slice.iter().map(|v| v.abs()).fold(0.0f32, f32::max).max(1e-6);
            scales[r] = max_abs;

            for c_block in 0..packed_cols {
                let mut word: u32 = 0;
                for bit_idx in 0..16 {
                    let c = c_block * 16 + bit_idx;
                    if c < cols {
                        let normalized = row_slice[c] / max_abs;
                        let code: u32 = match normalized {
                            v if v > 0.5 => 0b11,
                            v if v > 0.1 => 0b01,
                            v if v < -0.1 => 0b10,
                            _ => 0b00,
                        };
                        word |= code << (bit_idx * 2);
                    }
                }
                packed_data[r * packed_cols + c_block] = word;
            }
        }

        let original_bytes = rows * cols * 4;
        let compressed_bytes = packed_data.len() * 4 + scales.len() * 4;

        QuantizedTensor2Bit {
            rows,
            cols,
            packed_data,
            scales,
            original_bytes,
            compressed_bytes,
        }
    }

    /// [์ดˆ๊ณ ์† ๋ณ‘๋ ฌ 2-Bit GEMV]: Rayon ๋ฉ€ํ‹ฐ์ฝ”์–ด ๋ณ‘๋ ฌํ™” + ๋ฌด๋ถ„๊ธฐ ์–ธ๋กค๋ง(Branchless Unrolling)
    pub fn matvec_mul(&self, input: &[f32]) -> Vec<f32> {
        assert_eq!(input.len(), self.cols, "์ž…๋ ฅ ๋ฒกํ„ฐ ์ฐจ์› ๋ถˆ์ผ์น˜");
        let packed_cols = (self.cols + 15) / 16;

        // Rayon์„ ํ†ตํ•œ ํ–‰(Row) ๋‹จ์œ„ ์™„์ „ ๋ณ‘๋ ฌ ๋””์ŠคํŒจ์น˜
        (0..self.rows).into_par_iter().map(|r| {
            let row_offset = r * packed_cols;
            let mut sum: f32 = 0.0;
            let row_words = &self.packed_data[row_offset..row_offset + packed_cols];

            for (c_block, &word) in row_words.iter().enumerate() {
                if word == 0 { continue; } // Quiescent Sparsity: ์ „ ๋‰ด๋Ÿฐ ํœด์ง€๊ธฐ ์‹œ 16๊ฐœ ์—ฐ์‚ฐ ํ†ต์งธ๋กœ ์Šคํ‚ต!
                
                let base_c = c_block * 16;
                // 16๊ฐœ ๊ฐ€์ค‘์น˜ ๋ฌด๋ถ„๊ธฐ ์–ธ๋กค๋ง ์—ฐ์‚ฐ
                let limit = 16.min(self.cols.saturating_sub(base_c));
                let mut temp_word = word;
                for i in 0..limit {
                    let code = temp_word & 0b11;
                    temp_word >>= 2;
                    
                    // Branchless multiplier table
                    let m = match code {
                        0b01 => 1.0,
                        0b10 => -1.0,
                        0b11 => 2.0,
                        _ => 0.0,
                    };
                    if m != 0.0 {
                        sum += input[base_c + i] * m;
                    }
                }
            }
            sum * self.scales[r]
        }).collect()
    }

    pub fn compression_ratio(&self) -> f32 {
        self.original_bytes as f32 / self.compressed_bytes as f32
    }
}