abaddon 0.2.0-rc.1

Blazingly fast LLM inference engine - The Destroyer renders judgment
Documentation
//! GPU-accelerated LRDF (Low-Rank Distributed Factorization) encoder.
//!
//! Uses cuBLAS via cuda_svd for accelerated SVD computation, producing
//! fragments compatible with haagenti's LrdfDecoder.

/// CUDA-accelerated LRDF encoding using GPU SVD.
#[cfg(feature = "cuda")]
pub mod cuda {
    use cudarc::driver::CudaDevice;
    use std::sync::Arc;

    use crate::cuda_svd::cuda::GpuSvd;

    /// GPU-accelerated LRDF encoder.
    ///
    /// Produces fragments in the same format as haagenti's LrdfEncoder,
    /// but uses GPU-accelerated SVD via cuBLAS.
    pub struct GpuLrdfEncoder {
        gpu_svd: GpuSvd,
        num_fragments: u16,
        max_rank: usize,
        #[allow(dead_code)]
        seed: u64,
    }

    impl GpuLrdfEncoder {
        /// Create new GPU LRDF encoder.
        pub fn new(
            device: Arc<CudaDevice>,
            num_fragments: u16,
            seed: u64,
        ) -> Result<Self, Box<dyn std::error::Error>> {
            let gpu_svd = GpuSvd::new(device)?;
            Ok(Self {
                gpu_svd,
                num_fragments,
                max_rank: 64,
                seed,
            })
        }

        /// Set maximum rank for SVD approximation.
        pub fn with_max_rank(mut self, rank: usize) -> Self {
            self.max_rank = rank;
            self
        }

        /// Encode 2D matrix using GPU-accelerated distributed low-rank factorization.
        ///
        /// Returns fragments in haagenti-compatible format:
        /// - Header: rows (u32), cols (u32), num_components (u32)
        /// - Components: [sigma (f32), u_vector (f32 * rows), v_vector (f32 * cols)]
        pub fn encode_2d(
            &self,
            data: &[f32],
            rows: usize,
            cols: usize,
        ) -> Result<Vec<GpuHoloFragment>, Box<dyn std::error::Error>> {
            if data.len() != rows * cols {
                return Err("data size mismatch".into());
            }

            // Compute SVD with limited rank using GPU
            let rank = self.max_rank.min(rows.min(cols));
            let iterations = 20; // Same as haagenti
            let (u, s, v) = self
                .gpu_svd
                .svd_power_iteration(data, rows, cols, rank, iterations)?;

            // Distribute rank-1 components across fragments
            // Each fragment gets approximately rank/num_fragments components
            let components_per_frag =
                (rank + self.num_fragments as usize - 1) / self.num_fragments as usize;

            let mut fragments = Vec::with_capacity(self.num_fragments as usize);

            for frag_idx in 0..self.num_fragments {
                let start = frag_idx as usize * components_per_frag;
                let end = ((frag_idx as usize + 1) * components_per_frag).min(rank);

                if start >= rank {
                    // Empty fragment for this index
                    let mut frag_data = Vec::new();
                    frag_data.extend_from_slice(&(rows as u32).to_le_bytes());
                    frag_data.extend_from_slice(&(cols as u32).to_le_bytes());
                    frag_data.extend_from_slice(&0u32.to_le_bytes());
                    fragments.push(GpuHoloFragment::new(frag_idx, frag_data));
                    continue;
                }

                let num_components = end - start;
                let mut frag_data = Vec::new();

                // Header: rows, cols, num_components
                frag_data.extend_from_slice(&(rows as u32).to_le_bytes());
                frag_data.extend_from_slice(&(cols as u32).to_le_bytes());
                frag_data.extend_from_slice(&(num_components as u32).to_le_bytes());

                // Each component: sigma, u_vector, v_vector
                for r in start..end {
                    frag_data.extend_from_slice(&s[r].to_le_bytes());

                    for i in 0..rows {
                        frag_data.extend_from_slice(&u[i * rank + r].to_le_bytes());
                    }

                    for j in 0..cols {
                        frag_data.extend_from_slice(&v[j * rank + r].to_le_bytes());
                    }
                }

                fragments.push(GpuHoloFragment::new(frag_idx, frag_data));
            }

            Ok(fragments)
        }

        /// Encode FP8 E4M3 data directly on GPU (zero-copy path).
        ///
        /// Converts FP8→F32 on GPU, then runs SVD without CPU round-trip.
        /// This is 3-4x faster than encode_2d for FP8 models.
        pub fn encode_2d_fp8_e4m3(
            &self,
            fp8_data: &[u8],
            rows: usize,
            cols: usize,
            dtype_converter: &crate::gpu_dtype::cuda::GpuDtypeConverter,
        ) -> Result<Vec<GpuHoloFragment>, Box<dyn std::error::Error>> {
            if fp8_data.len() != rows * cols {
                return Err("FP8 data size mismatch".into());
            }

            // Convert FP8→F32 on GPU (stays on GPU)
            let d_f32 = dtype_converter.fp8_e4m3_to_f32(fp8_data)?;

            // Compute SVD directly from GPU data (no host round-trip)
            let rank = self.max_rank.min(rows.min(cols));
            let iterations = 20;
            let (u, s, v) = self
                .gpu_svd
                .svd_power_iteration_gpu(d_f32, rows, cols, rank, iterations)?;

            // Build fragments (same as encode_2d)
            let components_per_frag =
                (rank + self.num_fragments as usize - 1) / self.num_fragments as usize;
            let mut fragments = Vec::with_capacity(self.num_fragments as usize);

            for frag_idx in 0..self.num_fragments {
                let start = frag_idx as usize * components_per_frag;
                let end = ((frag_idx as usize + 1) * components_per_frag).min(rank);

                if start >= rank {
                    let mut frag_data = Vec::new();
                    frag_data.extend_from_slice(&(rows as u32).to_le_bytes());
                    frag_data.extend_from_slice(&(cols as u32).to_le_bytes());
                    frag_data.extend_from_slice(&0u32.to_le_bytes());
                    fragments.push(GpuHoloFragment::new(frag_idx, frag_data));
                    continue;
                }

                let num_components = end - start;
                let mut frag_data = Vec::new();

                frag_data.extend_from_slice(&(rows as u32).to_le_bytes());
                frag_data.extend_from_slice(&(cols as u32).to_le_bytes());
                frag_data.extend_from_slice(&(num_components as u32).to_le_bytes());

                for r in start..end {
                    frag_data.extend_from_slice(&s[r].to_le_bytes());
                    for i in 0..rows {
                        frag_data.extend_from_slice(&u[i * rank + r].to_le_bytes());
                    }
                    for j in 0..cols {
                        frag_data.extend_from_slice(&v[j * rank + r].to_le_bytes());
                    }
                }

                fragments.push(GpuHoloFragment::new(frag_idx, frag_data));
            }

            Ok(fragments)
        }
    }

    /// GPU-produced holographic fragment (compatible with haagenti's HoloFragment).
    #[derive(Debug, Clone)]
    pub struct GpuHoloFragment {
        /// Fragment index (0 to num_fragments-1).
        pub index: u16,
        /// Fragment data in haagenti format.
        pub data: Vec<u8>,
    }

    impl GpuHoloFragment {
        /// Create new fragment.
        pub fn new(index: u16, data: Vec<u8>) -> Self {
            Self { index, data }
        }

        /// Convert to haagenti's HoloFragment.
        pub fn to_haagenti(&self) -> haagenti::holotensor::HoloFragment {
            haagenti::holotensor::HoloFragment::new(self.index, self.data.clone())
        }
    }
}

/// Stub module when CUDA is not enabled.
#[cfg(not(feature = "cuda"))]
pub mod cuda {
    /// GPU LRDF encoder stub (requires CUDA feature).
    pub struct GpuLrdfEncoder;

    /// GPU-produced holographic fragment stub.
    #[derive(Debug, Clone)]
    pub struct GpuHoloFragment {
        /// Fragment index.
        pub index: u16,
        /// Fragment data.
        pub data: Vec<u8>,
    }

    impl GpuLrdfEncoder {
        /// Create new encoder (returns error without CUDA).
        pub fn new(
            _device: std::sync::Arc<()>,
            _num_fragments: u16,
            _seed: u64,
        ) -> Result<Self, std::io::Error> {
            Err(std::io::Error::new(
                std::io::ErrorKind::Unsupported,
                "CUDA not enabled",
            ))
        }

        /// Set maximum rank (no-op without CUDA).
        pub fn with_max_rank(self, _rank: usize) -> Self {
            self
        }

        /// Encode 2D matrix (returns error without CUDA).
        pub fn encode_2d(
            &self,
            _data: &[f32],
            _rows: usize,
            _cols: usize,
        ) -> Result<Vec<GpuHoloFragment>, std::io::Error> {
            Err(std::io::Error::new(
                std::io::ErrorKind::Unsupported,
                "CUDA not enabled",
            ))
        }
    }
}

#[cfg(test)]
#[cfg(feature = "cuda")]
mod tests {
    use super::cuda::*;
    use std::sync::Arc;

    #[test]
    fn test_gpu_lrdf_encoder() {
        // This test requires CUDA hardware
        let device = match cudarc::driver::CudaDevice::new(0) {
            Ok(d) => d,
            Err(_) => {
                eprintln!("Skipping GPU test: no CUDA device available");
                return;
            },
        };

        let encoder = GpuLrdfEncoder::new(device, 4, 42).unwrap().with_max_rank(8);

        // Create test matrix
        let rows = 64;
        let cols = 64;
        let data: Vec<f32> = (0..rows * cols).map(|i| (i as f32 * 0.01).sin()).collect();

        let fragments = encoder.encode_2d(&data, rows, cols).unwrap();

        assert_eq!(fragments.len(), 4);

        // Verify fragment format
        for (idx, frag) in fragments.iter().enumerate() {
            assert_eq!(frag.index, idx as u16);
            assert!(frag.data.len() >= 12); // At least header
        }
    }

    #[test]
    fn test_fragment_compatibility() {
        // Test that our fragments can be decoded by haagenti's decoder
        let device = match cudarc::driver::CudaDevice::new(0) {
            Ok(d) => d,
            Err(_) => {
                eprintln!("Skipping GPU test: no CUDA device available");
                return;
            },
        };

        let encoder = GpuLrdfEncoder::new(device, 4, 42)
            .unwrap()
            .with_max_rank(16);

        let rows = 32;
        let cols = 32;
        let data: Vec<f32> = (0..rows * cols).map(|i| (i as f32 * 0.01).sin()).collect();

        let gpu_fragments = encoder.encode_2d(&data, rows, cols).unwrap();

        // Convert to haagenti format
        let holo_fragments: Vec<_> = gpu_fragments.iter().map(|f| f.to_haagenti()).collect();

        // Use haagenti's decoder
        use haagenti::holotensor::LrdfDecoder;
        let mut decoder = LrdfDecoder::new(rows, cols, 4);

        for frag in &holo_fragments {
            decoder.add_fragment(frag).unwrap();
        }

        let reconstructed = decoder.reconstruct();
        assert_eq!(reconstructed.len(), data.len());

        // Calculate reconstruction quality (cosine similarity)
        let dot: f32 = data
            .iter()
            .zip(reconstructed.iter())
            .map(|(a, b)| a * b)
            .sum();
        let norm_a: f32 = data.iter().map(|x| x * x).sum::<f32>().sqrt();
        let norm_b: f32 = reconstructed.iter().map(|x| x * x).sum::<f32>().sqrt();
        let similarity = dot / (norm_a * norm_b);

        // Should have reasonable quality with rank 16 on 32x32 matrix
        assert!(
            similarity > 0.8,
            "Poor reconstruction quality: {}",
            similarity
        );
    }
}