minseok
โก Accelerate 2-Bit GEMV with Rayon Row-Parallelism and Quiescent Sparsity (790 Pass/sec)
d2ea1d1 | // ๐ฌ [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 | |
| } | |
| } | |