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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
//! 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
);
}
}