ipfrs_tensorlogic/gradient/
tensor.rs1use crate::arrow::{TensorDtype, TensorMetadata};
13use ipfrs_core::Cid;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16
17use super::GradientError;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct SparseGradient {
24 pub indices: Vec<usize>,
26 pub values: Vec<f32>,
28 pub shape: Vec<usize>,
30 pub metadata: TensorMetadata,
32}
33
34impl SparseGradient {
35 pub fn new(indices: Vec<usize>, values: Vec<f32>, shape: Vec<usize>) -> Self {
37 let metadata = TensorMetadata {
38 name: "sparse_gradient".to_string(),
39 shape: shape.clone(),
40 dtype: TensorDtype::Float32,
41 strides: None,
42 custom: HashMap::new(),
43 };
44
45 Self {
46 indices,
47 values,
48 shape,
49 metadata,
50 }
51 }
52
53 pub fn nnz(&self) -> usize {
55 self.indices.len()
56 }
57
58 pub fn total_elements(&self) -> usize {
60 self.shape.iter().product()
61 }
62
63 pub fn sparsity_ratio(&self) -> f32 {
65 1.0 - (self.nnz() as f32 / self.total_elements() as f32)
66 }
67
68 pub fn to_dense(&self) -> Vec<f32> {
70 let total = self.total_elements();
71 let mut dense = vec![0.0; total];
72
73 for (&idx, &val) in self.indices.iter().zip(&self.values) {
74 if idx < total {
75 dense[idx] = val;
76 }
77 }
78
79 dense
80 }
81
82 pub fn verify_shape(&self) -> Result<(), GradientError> {
84 let total = self.total_elements();
85
86 for &idx in &self.indices {
87 if idx >= total {
88 return Err(GradientError::InvalidGradient(format!(
89 "Index {} out of bounds for shape {:?}",
90 idx, self.shape
91 )));
92 }
93 }
94
95 if self.indices.len() != self.values.len() {
96 return Err(GradientError::InvalidGradient(format!(
97 "Indices length {} != values length {}",
98 self.indices.len(),
99 self.values.len()
100 )));
101 }
102
103 Ok(())
104 }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct QuantizedGradient {
112 pub quantized_values: Vec<i8>,
114 pub scale: f32,
116 pub min_val: f32,
118 pub shape: Vec<usize>,
120 pub metadata: TensorMetadata,
122}
123
124impl QuantizedGradient {
125 pub fn from_dense(values: &[f32], shape: Vec<usize>) -> Self {
127 let (quantized_values, scale, min_val) = Self::quantize_i8(values);
128
129 let metadata = TensorMetadata {
130 name: "quantized_gradient".to_string(),
131 shape: shape.clone(),
132 dtype: TensorDtype::Int8,
133 strides: None,
134 custom: HashMap::new(),
135 };
136
137 Self {
138 quantized_values,
139 scale,
140 min_val,
141 shape,
142 metadata,
143 }
144 }
145
146 fn quantize_i8(values: &[f32]) -> (Vec<i8>, f32, f32) {
148 if values.is_empty() {
149 return (Vec::new(), 1.0, 0.0);
150 }
151
152 let min_val = values.iter().copied().fold(f32::INFINITY, f32::min);
153 let max_val = values.iter().copied().fold(f32::NEG_INFINITY, f32::max);
154
155 let scale = if (max_val - min_val).abs() < 1e-8 {
157 1.0
158 } else {
159 (max_val - min_val) / 255.0
160 };
161
162 let quantized = values
163 .iter()
164 .map(|&v| {
165 let normalized = (v - min_val) / scale;
167 (normalized - 128.0).round().clamp(-128.0, 127.0) as i8
168 })
169 .collect();
170
171 (quantized, scale, min_val)
172 }
173
174 pub fn to_dense(&self) -> Vec<f32> {
176 self.quantized_values
177 .iter()
178 .map(|&q| {
179 let normalized = (q as f32) + 128.0;
181 normalized * self.scale + self.min_val
182 })
183 .collect()
184 }
185
186 pub fn compression_ratio(&self) -> f32 {
188 let original_size = self.quantized_values.len() * 4;
190 let compressed_size = self.quantized_values.len() + 8; original_size as f32 / compressed_size as f32
192 }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
199pub enum LayerGradient {
200 Dense { values: Vec<f32>, shape: Vec<usize> },
202 Sparse(SparseGradient),
204 Quantized(QuantizedGradient),
206}
207
208impl LayerGradient {
209 pub fn shape(&self) -> &[usize] {
211 match self {
212 LayerGradient::Dense { shape, .. } => shape,
213 LayerGradient::Sparse(sg) => &sg.shape,
214 LayerGradient::Quantized(qg) => &qg.shape,
215 }
216 }
217
218 pub fn to_dense(&self) -> Vec<f32> {
220 match self {
221 LayerGradient::Dense { values, .. } => values.clone(),
222 LayerGradient::Sparse(sg) => sg.to_dense(),
223 LayerGradient::Quantized(qg) => qg.to_dense(),
224 }
225 }
226
227 pub fn memory_size(&self) -> usize {
229 match self {
230 LayerGradient::Dense { values, .. } => values.len() * 4,
231 LayerGradient::Sparse(sg) => sg.indices.len() * 4 + sg.values.len() * 4,
232 LayerGradient::Quantized(qg) => qg.quantized_values.len() + 8,
233 }
234 }
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct GradientDelta {
242 #[serde(serialize_with = "crate::serialize_cid")]
244 #[serde(deserialize_with = "crate::deserialize_cid")]
245 pub base_model: Cid,
246 pub layer_gradients: HashMap<String, LayerGradient>,
248 pub checksum: u64,
250 pub timestamp: i64,
252}
253
254impl GradientDelta {
255 pub fn new(base_model: Cid) -> Self {
257 Self {
258 base_model,
259 layer_gradients: HashMap::new(),
260 checksum: 0,
261 timestamp: chrono::Utc::now().timestamp(),
262 }
263 }
264
265 pub fn add_dense_gradient(&mut self, layer_name: String, values: Vec<f32>, shape: Vec<usize>) {
267 self.layer_gradients
268 .insert(layer_name, LayerGradient::Dense { values, shape });
269 self.update_checksum();
270 }
271
272 pub fn add_sparse_gradient(&mut self, layer_name: String, gradient: SparseGradient) {
274 self.layer_gradients
275 .insert(layer_name, LayerGradient::Sparse(gradient));
276 self.update_checksum();
277 }
278
279 pub fn add_quantized_gradient(&mut self, layer_name: String, gradient: QuantizedGradient) {
281 self.layer_gradients
282 .insert(layer_name, LayerGradient::Quantized(gradient));
283 self.update_checksum();
284 }
285
286 fn update_checksum(&mut self) {
288 use std::collections::hash_map::DefaultHasher;
289 use std::hash::{Hash, Hasher};
290
291 let mut hasher = DefaultHasher::new();
292
293 self.layer_gradients.len().hash(&mut hasher);
295
296 let mut sorted_layers: Vec<_> = self.layer_gradients.iter().collect();
298 sorted_layers.sort_by_key(|(name, _)| *name);
299
300 for (name, gradient) in sorted_layers {
301 name.hash(&mut hasher);
302 gradient.shape().hash(&mut hasher);
303
304 let dense = gradient.to_dense();
306 let sample_size = dense.len().min(100);
307 for &v in dense.iter().take(sample_size) {
308 v.to_bits().hash(&mut hasher);
309 }
310 }
311
312 self.checksum = hasher.finish();
313 }
314
315 pub fn verify_checksum(&self) -> Result<(), GradientError> {
317 let mut temp = self.clone();
318 temp.update_checksum();
319
320 if temp.checksum == self.checksum {
321 Ok(())
322 } else {
323 Err(GradientError::ChecksumFailed)
324 }
325 }
326
327 pub fn total_memory_size(&self) -> usize {
329 self.layer_gradients.values().map(|g| g.memory_size()).sum()
330 }
331}
332
333pub struct GradientCompressor;
337
338impl GradientCompressor {
339 pub fn top_k(
341 values: &[f32],
342 shape: Vec<usize>,
343 k: usize,
344 ) -> Result<SparseGradient, GradientError> {
345 if k == 0 || k > values.len() {
346 return Err(GradientError::InvalidCompressionRatio(
347 k as f32 / values.len() as f32,
348 ));
349 }
350
351 let mut indexed_values: Vec<(usize, f32)> = values
353 .iter()
354 .enumerate()
355 .map(|(i, &v)| (i, v.abs()))
356 .collect();
357
358 indexed_values.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
359 indexed_values.truncate(k);
360
361 let mut indices = Vec::with_capacity(k);
362 let mut sparse_values = Vec::with_capacity(k);
363
364 for (idx, _) in indexed_values {
365 indices.push(idx);
366 sparse_values.push(values[idx]);
367 }
368
369 Ok(SparseGradient::new(indices, sparse_values, shape))
370 }
371
372 pub fn threshold(values: &[f32], shape: Vec<usize>, threshold: f32) -> SparseGradient {
374 let mut indices = Vec::new();
375 let mut sparse_values = Vec::new();
376
377 for (i, &v) in values.iter().enumerate() {
378 if v.abs() >= threshold {
379 indices.push(i);
380 sparse_values.push(v);
381 }
382 }
383
384 SparseGradient::new(indices, sparse_values, shape)
385 }
386
387 pub fn quantize(values: &[f32], shape: Vec<usize>) -> QuantizedGradient {
389 QuantizedGradient::from_dense(values, shape)
390 }
391
392 pub fn random_sparsification(
394 values: &[f32],
395 shape: Vec<usize>,
396 keep_ratio: f32,
397 ) -> Result<SparseGradient, GradientError> {
398 use rand::RngExt;
399
400 if keep_ratio <= 0.0 || keep_ratio > 1.0 {
401 return Err(GradientError::InvalidCompressionRatio(keep_ratio));
402 }
403
404 let mut rng = rand::rng();
405 let mut indices = Vec::new();
406 let mut sparse_values = Vec::new();
407
408 for (i, &v) in values.iter().enumerate() {
409 if rng.random::<f32>() < keep_ratio {
410 indices.push(i);
411 sparse_values.push(v / keep_ratio); }
413 }
414
415 Ok(SparseGradient::new(indices, sparse_values, shape))
416 }
417}
418
419pub struct GradientAggregator;
423
424impl GradientAggregator {
425 pub fn average(gradients: &[Vec<f32>]) -> Result<Vec<f32>, GradientError> {
427 if gradients.is_empty() {
428 return Err(GradientError::EmptyGradientSet);
429 }
430
431 let len = gradients[0].len();
432
433 for g in gradients.iter() {
435 if g.len() != len {
436 return Err(GradientError::ShapeMismatch {
437 expected: vec![len],
438 actual: vec![g.len()],
439 });
440 }
441 }
442
443 let mut result = vec![0.0; len];
444 let count = gradients.len() as f32;
445
446 for gradient in gradients {
447 for (i, &v) in gradient.iter().enumerate() {
448 result[i] += v / count;
449 }
450 }
451
452 Ok(result)
453 }
454
455 pub fn weighted_average(
457 gradients: &[Vec<f32>],
458 weights: &[f32],
459 ) -> Result<Vec<f32>, GradientError> {
460 if gradients.is_empty() {
461 return Err(GradientError::EmptyGradientSet);
462 }
463
464 if gradients.len() != weights.len() {
465 return Err(GradientError::InvalidGradient(format!(
466 "Gradient count {} != weight count {}",
467 gradients.len(),
468 weights.len()
469 )));
470 }
471
472 let len = gradients[0].len();
473
474 for g in gradients.iter() {
476 if g.len() != len {
477 return Err(GradientError::ShapeMismatch {
478 expected: vec![len],
479 actual: vec![g.len()],
480 });
481 }
482 }
483
484 let weight_sum: f32 = weights.iter().sum();
485 if weight_sum == 0.0 {
486 return Err(GradientError::InvalidGradient(
487 "Sum of weights is zero".to_string(),
488 ));
489 }
490
491 let mut result = vec![0.0; len];
492
493 for (gradient, &weight) in gradients.iter().zip(weights) {
494 let normalized_weight = weight / weight_sum;
495 for (i, &v) in gradient.iter().enumerate() {
496 result[i] += v * normalized_weight;
497 }
498 }
499
500 Ok(result)
501 }
502
503 pub fn apply_momentum(
505 current_gradient: &[f32],
506 previous_momentum: &[f32],
507 momentum_factor: f32,
508 ) -> Result<Vec<f32>, GradientError> {
509 if current_gradient.len() != previous_momentum.len() {
510 return Err(GradientError::ShapeMismatch {
511 expected: vec![previous_momentum.len()],
512 actual: vec![current_gradient.len()],
513 });
514 }
515
516 let result = current_gradient
517 .iter()
518 .zip(previous_momentum)
519 .map(|(&g, &m)| momentum_factor * m + g)
520 .collect();
521
522 Ok(result)
523 }
524}
525
526pub struct GradientVerifier;
530
531impl GradientVerifier {
532 pub fn verify_shape(gradient: &[f32], expected_shape: &[usize]) -> Result<(), GradientError> {
534 let expected_size: usize = expected_shape.iter().product();
535
536 if gradient.len() != expected_size {
537 return Err(GradientError::ShapeMismatch {
538 expected: expected_shape.to_vec(),
539 actual: vec![gradient.len()],
540 });
541 }
542
543 Ok(())
544 }
545
546 pub fn detect_outliers(gradient: &[f32], std_threshold: f32) -> Result<(), GradientError> {
548 if gradient.is_empty() {
549 return Ok(());
550 }
551
552 let mean = gradient.iter().sum::<f32>() / gradient.len() as f32;
554
555 let variance =
557 gradient.iter().map(|&v| (v - mean).powi(2)).sum::<f32>() / gradient.len() as f32;
558 let std_dev = variance.sqrt();
559
560 for (i, &v) in gradient.iter().enumerate() {
562 let z_score = (v - mean).abs() / std_dev;
563 if z_score > std_threshold {
564 return Err(GradientError::OutlierDetected { index: i, value: v });
565 }
566 }
567
568 Ok(())
569 }
570
571 pub fn verify_finite(gradient: &[f32]) -> Result<(), GradientError> {
573 for (i, &v) in gradient.iter().enumerate() {
574 if !v.is_finite() {
575 return Err(GradientError::InvalidGradient(format!(
576 "Non-finite value at index {}: {}",
577 i, v
578 )));
579 }
580 }
581
582 Ok(())
583 }
584
585 pub fn l2_norm(gradient: &[f32]) -> f32 {
587 gradient.iter().map(|&v| v * v).sum::<f32>().sqrt()
588 }
589
590 pub fn clip_by_norm(gradient: &mut [f32], max_norm: f32) {
592 let norm = Self::l2_norm(gradient);
593
594 if norm > max_norm {
595 let scale = max_norm / norm;
596 for v in gradient.iter_mut() {
597 *v *= scale;
598 }
599 }
600 }
601}