// ๐Ÿ”ฌ [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, // ์—ด(col) ๋ฐฉํ–ฅ์œผ๋กœ 16๊ฐœ ๊ฐ€์ค‘์น˜๋งˆ๋‹ค 1๊ฐœ uint32 ํŒจํ‚น pub scales: Vec, // ํ–‰(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 { 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 } }