1#![allow(dead_code)]
8use crate::{CooTensor, CsrTensor, SparseTensor, TorshResult};
9use std::collections::HashMap;
10use torsh_core::TorshError;
11
12pub trait SparseOptimizer {
14 fn step(
16 &mut self,
17 parameters: &mut [&mut CsrTensor],
18 gradients: &[&CsrTensor],
19 ) -> TorshResult<()>;
20
21 fn zero_grad(&mut self) {}
23
24 fn lr(&self) -> f32;
26
27 fn set_lr(&mut self, lr: f32);
29}
30
31pub struct SparseSGD {
36 lr: f32,
38 momentum: f32,
40 weight_decay: f32,
42 nesterov: bool,
44 momentum_buffers: Vec<Option<CsrTensor>>,
46}
47
48impl SparseSGD {
49 pub fn new(lr: f32, momentum: f32, weight_decay: f32, nesterov: bool) -> Self {
51 Self {
52 lr,
53 momentum,
54 weight_decay,
55 nesterov,
56 momentum_buffers: Vec::new(),
57 }
58 }
59
60 pub fn default(lr: f32) -> Self {
62 Self::new(lr, 0.0, 0.0, false)
63 }
64}
65
66impl SparseOptimizer for SparseSGD {
67 fn step(
68 &mut self,
69 parameters: &mut [&mut CsrTensor],
70 gradients: &[&CsrTensor],
71 ) -> TorshResult<()> {
72 if parameters.len() != gradients.len() {
73 return Err(TorshError::InvalidArgument(
74 "Number of parameters and gradients must match".to_string(),
75 ));
76 }
77
78 while self.momentum_buffers.len() < parameters.len() {
80 self.momentum_buffers.push(None);
81 }
82
83 for (_i, (param, grad)) in parameters.iter_mut().zip(gradients.iter()).enumerate() {
84 let update_coo = grad.to_coo()?;
86 let update_triplets = update_coo.triplets();
87
88 let param_coo = param.to_coo()?;
90 let param_triplets = param_coo.triplets();
91
92 let mut updated_positions = HashMap::new();
94
95 for (row, col, grad_val) in &update_triplets {
97 updated_positions.insert((*row, *col), -self.lr * grad_val);
98 }
99
100 for (row, col, param_val) in ¶m_triplets {
102 if let Some(update_val) = updated_positions.get(&(*row, *col)) {
103 updated_positions.insert((*row, *col), param_val + update_val);
104 } else {
105 updated_positions.insert((*row, *col), *param_val);
106 }
107 }
108
109 let (rows, cols, values): (Vec<_>, Vec<_>, Vec<_>) = updated_positions
111 .into_iter()
112 .map(|((r, c), v)| (r, c, v))
113 .fold(
114 (Vec::new(), Vec::new(), Vec::new()),
115 |(mut rows, mut cols, mut vals), (r, c, v)| {
116 rows.push(r);
117 cols.push(c);
118 vals.push(v);
119 (rows, cols, vals)
120 },
121 );
122
123 let updated_coo = CooTensor::new(rows, cols, values, param.shape().clone())?;
124 **param = CsrTensor::from_coo(&updated_coo)?;
125 }
126
127 Ok(())
128 }
129
130 fn lr(&self) -> f32 {
131 self.lr
132 }
133
134 fn set_lr(&mut self, lr: f32) {
135 self.lr = lr;
136 }
137}
138
139pub struct SparseAdam {
143 lr: f32,
145 beta1: f32,
147 beta2: f32,
149 eps: f32,
151 weight_decay: f32,
153 amsgrad: bool,
155 step_count: usize,
157 first_moments: Vec<Option<CsrTensor>>,
159 second_moments: Vec<Option<CsrTensor>>,
161 max_second_moments: Vec<Option<CsrTensor>>,
163}
164
165impl SparseAdam {
166 pub fn new(
168 lr: f32,
169 beta1: f32,
170 beta2: f32,
171 eps: f32,
172 weight_decay: f32,
173 amsgrad: bool,
174 ) -> Self {
175 Self {
176 lr,
177 beta1,
178 beta2,
179 eps,
180 weight_decay,
181 amsgrad,
182 step_count: 0,
183 first_moments: Vec::new(),
184 second_moments: Vec::new(),
185 max_second_moments: Vec::new(),
186 }
187 }
188
189 pub fn default(lr: f32) -> Self {
191 Self::new(lr, 0.9, 0.999, 1e-8, 0.0, false)
192 }
193}
194
195impl SparseOptimizer for SparseAdam {
196 fn step(
197 &mut self,
198 parameters: &mut [&mut CsrTensor],
199 gradients: &[&CsrTensor],
200 ) -> TorshResult<()> {
201 if parameters.len() != gradients.len() {
202 return Err(TorshError::InvalidArgument(
203 "Number of parameters and gradients must match".to_string(),
204 ));
205 }
206
207 self.step_count += 1;
208
209 while self.first_moments.len() < parameters.len() {
211 self.first_moments.push(None);
212 self.second_moments.push(None);
213 if self.amsgrad {
214 self.max_second_moments.push(None);
215 }
216 }
217
218 let bias_correction1 = 1.0 - self.beta1.powi(self.step_count as i32);
220 let bias_correction2 = 1.0 - self.beta2.powi(self.step_count as i32);
221
222 for (_i, (param, grad)) in parameters.iter_mut().zip(gradients.iter()).enumerate() {
223 let grad_coo = grad.to_coo()?;
225 let grad_triplets = grad_coo.triplets();
226
227 let mut param_updates = HashMap::new();
229
230 for (row, col, grad_val) in &grad_triplets {
231 let update_val =
233 -self.lr * grad_val / (bias_correction1 * bias_correction2.sqrt() + self.eps);
234 param_updates.insert((*row, *col), update_val);
235 }
236
237 let param_coo = param.to_coo()?;
239 let mut updated_positions = HashMap::new();
240
241 for (row, col, param_val) in param_coo.triplets() {
243 updated_positions.insert((row, col), param_val);
244 }
245
246 for ((row, col), update_val) in param_updates {
248 *updated_positions.entry((row, col)).or_insert(0.0) += update_val;
249 }
250
251 let (rows, cols, values): (Vec<_>, Vec<_>, Vec<_>) = updated_positions
253 .into_iter()
254 .map(|((r, c), v)| (r, c, v))
255 .fold(
256 (Vec::new(), Vec::new(), Vec::new()),
257 |(mut rows, mut cols, mut vals), (r, c, v)| {
258 rows.push(r);
259 cols.push(c);
260 vals.push(v);
261 (rows, cols, vals)
262 },
263 );
264
265 let updated_coo = CooTensor::new(rows, cols, values, param.shape().clone())?;
266 **param = CsrTensor::from_coo(&updated_coo)?;
267 }
268
269 Ok(())
270 }
271
272 fn lr(&self) -> f32 {
273 self.lr
274 }
275
276 fn set_lr(&mut self, lr: f32) {
277 self.lr = lr;
278 }
279}
280
281pub struct SparseAdamW {
285 lr: f32,
287 beta1: f32,
289 beta2: f32,
291 eps: f32,
293 weight_decay: f32,
295 amsgrad: bool,
297 step_count: usize,
299 first_moments: Vec<Option<CsrTensor>>,
301 second_moments: Vec<Option<CsrTensor>>,
303 max_second_moments: Vec<Option<CsrTensor>>,
305}
306
307impl SparseAdamW {
308 pub fn new(
310 lr: f32,
311 beta1: f32,
312 beta2: f32,
313 eps: f32,
314 weight_decay: f32,
315 amsgrad: bool,
316 ) -> Self {
317 Self {
318 lr,
319 beta1,
320 beta2,
321 eps,
322 weight_decay,
323 amsgrad,
324 step_count: 0,
325 first_moments: Vec::new(),
326 second_moments: Vec::new(),
327 max_second_moments: Vec::new(),
328 }
329 }
330
331 pub fn default(lr: f32) -> Self {
333 Self::new(lr, 0.9, 0.999, 1e-8, 0.01, false)
334 }
335}
336
337impl SparseOptimizer for SparseAdamW {
338 fn step(
339 &mut self,
340 parameters: &mut [&mut CsrTensor],
341 gradients: &[&CsrTensor],
342 ) -> TorshResult<()> {
343 if parameters.len() != gradients.len() {
344 return Err(TorshError::InvalidArgument(
345 "Number of parameters and gradients must match".to_string(),
346 ));
347 }
348
349 self.step_count += 1;
350
351 while self.first_moments.len() < parameters.len() {
353 self.first_moments.push(None);
354 self.second_moments.push(None);
355 if self.amsgrad {
356 self.max_second_moments.push(None);
357 }
358 }
359
360 for (_i, (param, grad)) in parameters.iter_mut().zip(gradients.iter()).enumerate() {
361 let grad_coo = grad.to_coo()?;
363 let param_coo = param.to_coo()?;
364
365 let mut updated_positions = HashMap::new();
366
367 for (row, col, param_val) in param_coo.triplets() {
369 let decayed_val = param_val * (1.0 - self.lr * self.weight_decay);
370 updated_positions.insert((row, col), decayed_val);
371 }
372
373 for (row, col, grad_val) in grad_coo.triplets() {
375 let update_val = -self.lr * grad_val; *updated_positions.entry((row, col)).or_insert(0.0) += update_val;
377 }
378
379 let (rows, cols, values): (Vec<_>, Vec<_>, Vec<_>) = updated_positions
381 .into_iter()
382 .map(|((r, c), v)| (r, c, v))
383 .fold(
384 (Vec::new(), Vec::new(), Vec::new()),
385 |(mut rows, mut cols, mut vals), (r, c, v)| {
386 rows.push(r);
387 cols.push(c);
388 vals.push(v);
389 (rows, cols, vals)
390 },
391 );
392
393 let updated_coo = CooTensor::new(rows, cols, values, param.shape().clone())?;
394 **param = CsrTensor::from_coo(&updated_coo)?;
395 }
396
397 Ok(())
398 }
399
400 fn lr(&self) -> f32 {
401 self.lr
402 }
403
404 fn set_lr(&mut self, lr: f32) {
405 self.lr = lr;
406 }
407}
408
409pub struct SparseRMSprop {
413 lr: f32,
415 alpha: f32,
417 eps: f32,
419 weight_decay: f32,
421 momentum: f32,
423 centered: bool,
425 step_count: usize,
427 square_averages: Vec<Option<CsrTensor>>,
429 momentum_buffers: Vec<Option<CsrTensor>>,
431 grad_averages: Vec<Option<CsrTensor>>,
433}
434
435impl SparseRMSprop {
436 pub fn new(
438 lr: f32,
439 alpha: f32,
440 eps: f32,
441 weight_decay: f32,
442 momentum: f32,
443 centered: bool,
444 ) -> Self {
445 Self {
446 lr,
447 alpha,
448 eps,
449 weight_decay,
450 momentum,
451 centered,
452 step_count: 0,
453 square_averages: Vec::new(),
454 momentum_buffers: Vec::new(),
455 grad_averages: Vec::new(),
456 }
457 }
458
459 pub fn default(lr: f32) -> Self {
461 Self::new(lr, 0.99, 1e-8, 0.0, 0.0, false)
462 }
463}
464
465impl SparseOptimizer for SparseRMSprop {
466 fn step(
467 &mut self,
468 parameters: &mut [&mut CsrTensor],
469 gradients: &[&CsrTensor],
470 ) -> TorshResult<()> {
471 if parameters.len() != gradients.len() {
472 return Err(TorshError::InvalidArgument(
473 "Number of parameters and gradients must match".to_string(),
474 ));
475 }
476
477 self.step_count += 1;
478
479 while self.square_averages.len() < parameters.len() {
481 self.square_averages.push(None);
482 if self.momentum > 0.0 {
483 self.momentum_buffers.push(None);
484 }
485 if self.centered {
486 self.grad_averages.push(None);
487 }
488 }
489
490 for (_i, (param, grad)) in parameters.iter_mut().zip(gradients.iter()).enumerate() {
491 let grad_coo = grad.to_coo()?;
493 let param_coo = param.to_coo()?;
494
495 let mut updated_positions = HashMap::new();
496
497 for (row, col, param_val) in param_coo.triplets() {
499 updated_positions.insert((row, col), param_val);
500 }
501
502 for (row, col, grad_val) in grad_coo.triplets() {
504 let update_val = -self.lr * grad_val / (self.eps + 1.0); *updated_positions.entry((row, col)).or_insert(0.0) += update_val;
506 }
507
508 let (rows, cols, values): (Vec<_>, Vec<_>, Vec<_>) = updated_positions
510 .into_iter()
511 .map(|((r, c), v)| (r, c, v))
512 .fold(
513 (Vec::new(), Vec::new(), Vec::new()),
514 |(mut rows, mut cols, mut vals), (r, c, v)| {
515 rows.push(r);
516 cols.push(c);
517 vals.push(v);
518 (rows, cols, vals)
519 },
520 );
521
522 let updated_coo = CooTensor::new(rows, cols, values, param.shape().clone())?;
523 **param = CsrTensor::from_coo(&updated_coo)?;
524 }
525
526 Ok(())
527 }
528
529 fn lr(&self) -> f32 {
530 self.lr
531 }
532
533 fn set_lr(&mut self, lr: f32) {
534 self.lr = lr;
535 }
536}