1use rayon::prelude::*;
7
8pub struct AdamState {
10 pub m: Vec<[f32; 2]>, pub v: Vec<[f32; 2]>, }
13
14impl AdamState {
15 pub fn new(n: usize) -> Self {
16 Self {
17 m: vec![[0.0; 2]; n],
18 v: vec![[0.0; 2]; n],
19 }
20 }
21}
22
23pub fn adam_step(
27 embedding: &mut [[f32; 2]],
28 grad: &[[f32; 2]],
29 state: &mut AdamState,
30 t: usize,
31 lr: f32,
32) {
33 let beta1 = 0.9_f32;
34 let beta2 = 0.999_f32;
35 let eps = 1e-7_f32;
36 let t = t as f32;
37
38 let bc1 = 1.0 - beta1.powf(t);
40 let bc2 = 1.0 - beta2.powf(t);
41 let lr_t = lr * bc2.sqrt() / bc1;
42
43 embedding
45 .par_chunks_mut(4096)
46 .zip(grad.par_chunks(4096))
47 .zip(state.m.par_chunks_mut(4096))
48 .zip(state.v.par_chunks_mut(4096))
49 .for_each(|(((y_chunk, g_chunk), m_chunk), v_chunk)| {
50 for (((y, g), m), v) in y_chunk
51 .iter_mut()
52 .zip(g_chunk)
53 .zip(m_chunk.iter_mut())
54 .zip(v_chunk.iter_mut())
55 {
56 for dim in 0..2 {
57 m[dim] = beta1 * m[dim] + (1.0 - beta1) * g[dim];
58 v[dim] = beta2 * v[dim] + (1.0 - beta2) * g[dim] * g[dim];
59 y[dim] -= lr_t * m[dim] / (v[dim].sqrt() + eps);
60 }
61 }
62 });
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn adam_moves_in_gradient_direction() {
71 let n = 4;
72 let mut emb = vec![[0.0_f32; 2]; n];
73 let grad = vec![[1.0_f32, -1.0]; n]; let mut state = AdamState::new(n);
75
76 adam_step(&mut emb, &grad, &mut state, 1, 1.0);
77
78 for y in &emb {
80 assert!(y[0] < 0.0, "should move in -x direction");
81 assert!(y[1] > 0.0, "should move in +y direction");
82 }
83 }
84}