Skip to main content

lumen_core/ops/
nn.rs

1use crate::{Tensor, AutogradMetaT, Dim, FloatDType, Layout, Storage, StorageIndices, D};
2use super::reduce::DimArray;
3
4// ==============================================================================================//
5//                             RMS Norm
6// ==============================================================================================//
7impl<T: FloatDType> Tensor<T> {
8    /// RMS Norm
9    /// - input: (..., dim)
10    /// - weight: (dim,)
11    pub fn rms_norm(input: &Tensor<T>, weight: &Tensor<T>, eps: T) -> crate::Result<Tensor<T>> {
12        // 1. Calculate Mean of Squares: Mean(x^2)
13        let variance = input.sqr()?.mean_keepdim(D::Minus1)?;
14    
15        // 2. Calculate RMS: sqrt(variance + eps)
16        let rms = (variance + eps).sqrt()?;
17    
18        // 3. Normalize: x / rms
19        let input_normalized = input.broadcast_div(&rms)?;
20    
21        // 4. Scale: x * weight
22        let out = input_normalized.broadcast_mul(weight)?;
23    
24        Ok(out)
25    }
26
27    /// RMS Norm
28    /// - input: (..., dim)
29    /// - weight: (dim,)
30    pub fn rms_norm_fused(input: &Tensor<T>, weight: &Tensor<T>, eps: T) -> crate::Result<Tensor<T>> {
31        let last_dim = D::Minus1.to_index(input.shape(), "rms_norm")?;
32        let input_layout = input.layout();
33        let last_dim_stride = input_layout.stride()[last_dim];
34        let last_dim_size = input_layout.dims()[last_dim];
35        let weight_size = weight.dims1()?;
36        if last_dim_size != weight_size {
37            crate::bail!("input dim size != weight size in rms norm");
38        }
39
40        let input_storage = input.storage_read()?;
41        let input_data = input_storage.data();
42
43        let weight_storage = weight.storage_read()?;
44        let weight_data = weight_storage.data();
45        let weight_data = &weight_data[weight.layout().start_offset..];
46        let weight_stride = weight.layout().stride()[0];        
47        let mut output_data = vec![T::ZERO; input.element_count()];
48    
49        let batch_size = output_data.len() / last_dim_size;
50        match input_layout.storage_indices() {
51            StorageIndices::Contiguous(index) => {
52                let input_data = &input_data[index.begin_index..index.end_index];
53                for b in 0..batch_size {
54                    let input_data = &input_data[b*last_dim_size..b*last_dim_size+last_dim_size];
55                    let output_data = &mut output_data[b*last_dim_size..b*last_dim_size+last_dim_size];
56                    
57                    let variance = input_data.iter().cloned().map(|v| v.sqr()).sum::<T>() / T::from_usize(last_dim_size);
58                    let rms = (variance + eps).sqrt();
59
60                    for i in 0..last_dim_size {
61                        output_data[i] = input_data[i] / rms * weight_data[i * weight_stride];
62                    }
63                }
64            }
65            StorageIndices::Uncontiguous(_) => {
66                let layout = input_layout.narrow(last_dim, 0, 1)?;
67                println!("{}", layout.storage_indices().count());
68                for (b, src_index) in layout.storage_indices().enumerate() {
69                    let output_data = &mut output_data[b*last_dim_size..b*last_dim_size+last_dim_size];
70                    let input_arr: DimArray<'_, T> = DimArray {
71                        src: &input_data[src_index..],
72                        size: last_dim_size,
73                        stride: last_dim_stride,
74                    };
75
76                    let variance = input_arr.clone().into_iter().map(|v| v.sqr()).sum::<T>() / T::from_usize(last_dim_size);
77                    let rms = (variance + eps).sqrt();
78
79                    for (i, v) in (0..last_dim_size).into_iter().zip(input_arr.into_iter()) {
80                        output_data[i] = v / rms * weight_data[i * weight_stride];
81                    }
82                }
83            }
84        }
85
86        let output_storage = Storage::new(output_data);
87        let meta = T::AutogradMeta::on_rms_norm_op(input, weight, eps);
88        let output = Tensor::from_storage(output_storage, input.shape(), meta);
89        Ok(output)
90    }
91
92    pub(crate) fn rms_norm_backward_fused(
93        grad_output: &Tensor<T>,
94        input: &Tensor<T>,      
95        weight: &Tensor<T>, 
96        input_sum_grad: &Tensor<T>,
97        weight_sum_grad: &Tensor<T>,
98        eps: T,
99    ) -> crate::Result<()> {
100        let shape = input.shape();
101        let last_dim = shape.dims().len() - 1;
102        let last_dim_size = shape.dims()[last_dim];
103        let batch_size = input.element_count() / last_dim_size;
104        let d_inv = T::from_f64(1.0 / last_dim_size as f64);
105
106        // 获取数据引用
107        let g_data_storage = grad_output.storage_read()?;
108        let g_data = g_data_storage.data();
109        assert!(grad_output.is_contiguous());
110
111        let x_data_storage = input.storage_read()?;
112        let x_data = x_data_storage.data();
113
114        let w_data_storage = weight.storage_read()?;
115        let w_data = w_data_storage.data();
116        let w_data = &w_data[weight.layout().start_offset..];
117        let w_stride = weight.layout().stride()[0];
118
119        // 准备输出结果,直接分配内存
120        assert!(input_sum_grad.is_contiguous());
121        assert!(weight_sum_grad.is_contiguous());
122        let mut input_grad_storage = input_sum_grad.storage_write()?;
123        let mut weight_gard_storage = weight_sum_grad.storage_write()?;
124        let input_gard_data = input_grad_storage.data_mut();
125        let weight_gard_data = weight_gard_storage.data_mut();
126
127        if input.is_contiguous() {
128            let x_data = &x_data[input.layout().start_offset..];
129            // 遍历每个 Batch (每一行数据)
130            for b in 0..batch_size {
131                let offset = b * last_dim_size;
132                let g_row = &g_data[offset..offset + last_dim_size];
133                let x_row = &x_data[offset..offset + last_dim_size];
134
135                let input_gard_row_data = &mut input_gard_data[offset..offset + last_dim_size];
136
137                // --- 第一次遍历:计算 v (方差) 和 s (点积和) ---
138                let mut sum_sq = T::ZERO;
139                let mut sum_dot = T::ZERO;
140                
141                for i in 0..last_dim_size {
142                    let xi = x_row[i];
143                    let wi = w_data[i * w_stride];
144                    let gi = g_row[i];
145                    
146                    sum_sq = sum_sq + xi * xi;
147                    // 注意:这里的 gi * wi * xi 是为了计算输入的梯度
148                    sum_dot = sum_dot + gi * wi * xi;
149                }
150                
151                let variance = sum_sq * d_inv + eps;
152                let rsqrt_v = T::one() / variance.sqrt(); // 1/rms
153                let s_v = (sum_dot * d_inv) / variance;   // s/v
154
155                // --- 第二次遍历:计算 dx 和累加 dw ---
156                for i in 0..last_dim_size {
157                    let xi = x_row[i];
158                    let wi = w_data[i * w_stride];
159                    let gi = g_row[i];
160
161                    // 计算 dx: (wi / rms) * (gi - xi * s / v)
162                    // input_gard_row_data[i] += (wi * rsqrt_v) * (gi - xi * s_v);
163                    input_gard_row_data[i] += rsqrt_v * (wi * gi - xi * s_v);
164
165                    // 计算 dw: gi * (xi / rms) -> 并在 batch 间累加
166                    weight_gard_data[i] += gi * (xi * rsqrt_v);
167                }
168            }
169        } else {
170            unimplemented!("rms norm backward in un continue")
171        }
172
173        Ok(())
174    }
175
176    pub(crate) fn softmax_backward_fused(
177        grad_output: &Tensor<T>, 
178        output: &Tensor<T>, 
179        input_grad: &Tensor<T>, 
180        dim: usize,
181    ) -> crate::Result<()> {
182        let reduce_dim = dim;
183        let shape = output.shape();
184        let reduce_dim_size = shape.dims()[reduce_dim];
185        let element_count = output.layout().element_count();
186
187        // 获取只读数据引用
188        let g_data_storage = grad_output.storage_read()?;
189        let g_data = g_data_storage.data();
190
191        let y_data_storage = output.storage_read()?;
192        let y_data = y_data_storage.data();
193
194        // 准备输出梯度的目标(获取可写引用)
195        assert!(input_grad.is_contiguous());
196        let mut dx_storage = input_grad.storage_write()?;
197        let dx_data = dx_storage.data_mut();
198
199        // 获取 Stride 来判断是否可以用 Fast Path
200        let y_stride = output.layout().stride()[reduce_dim];
201        let g_stride = grad_output.layout().stride()[reduce_dim];
202
203        if output.is_contiguous() && grad_output.is_contiguous() && y_stride == 1 && g_stride == 1 {
204            let y_data = &y_data[output.layout().start_offset..output.layout().start_offset + element_count];
205            let g_data = &g_data[grad_output.layout().start_offset..grad_output.layout().start_offset + element_count];
206            let dx_data = &mut dx_data[input_grad.layout().start_offset..input_grad.layout().start_offset + element_count];
207
208            let batch_size = element_count / reduce_dim_size;
209
210            // 遍历每个 Batch
211            for b in 0..batch_size {
212                let offset = b * reduce_dim_size;
213                let g_row = &g_data[offset..offset + reduce_dim_size];
214                let y_row = &y_data[offset..offset + reduce_dim_size];
215                let dx_row = &mut dx_data[offset..offset + reduce_dim_size];
216
217                // 计算 sum(dy * y) 
218                let mut sum_gy = T::ZERO; 
219                for i in 0..reduce_dim_size {
220                    sum_gy = sum_gy + g_row[i] * y_row[i];
221                }
222
223                // 计算 dx 并累加 
224                // 公式: dx_i = y_i * (dy_i - sum(dy * y))
225                for i in 0..reduce_dim_size {
226                    let dy_i = g_row[i];
227                    let y_i = y_row[i];
228                    dx_row[i] += y_i * (dy_i - sum_gy);
229                }
230            }
231        } else {
232            unimplemented!("softmax backward in non-contiguous memory is not implemented yet")
233        }
234
235        Ok(())
236    }
237}
238
239impl<T: FloatDType> Tensor<T> {
240    pub fn softmax<D: Dim>(&self, dim: D) -> crate::Result<Tensor<T>> {
241        let dim = dim.to_index(self.shape(), "softmax")?;
242        let max = self.max_keepdim(dim)?; // (..., 1, ...)
243        let diff = self.broadcast_sub(&max)?;   // (..., D, ...)
244        let num = diff.exp()?;  // (..., D, ...)
245        let den = num.sum_keepdim(dim)?;
246        let out = num.broadcast_div(&den)?;
247        Ok(out)
248    }
249
250    pub fn softmax_fused<D: Dim>(&self, dim: D) -> crate::Result<Tensor<T>> {
251        let reduce_dim = dim.to_index(self.shape(), "softmax_fused")?;
252        assert!(reduce_dim < self.layout().dims().len());
253    
254        let reduce_dim_stride = self.layout().stride()[reduce_dim];
255        let reduce_dim_size = self.layout().dims()[reduce_dim];
256    
257        let src_storage = self.storage_read()?;
258        let src_data = src_storage.data();
259        let src_start_offset = self.layout().start_offset;
260    
261        let element_count = self.layout().element_count();
262    
263        let mut dst: Vec<T> = Vec::with_capacity(element_count);
264        unsafe { dst.set_len(element_count) };
265    
266        // fast path: contiguous
267        if self.is_contiguous() && reduce_dim_stride == 1 {
268            let src_data = &src_data[src_start_offset..src_start_offset + element_count];
269    
270            for (src_chunk, dst_chunk) in
271                src_data.chunks(reduce_dim_size).zip(dst.chunks_mut(reduce_dim_size))
272            {
273                // ===== pass 1: online max + sum =====
274                let mut m = src_chunk[0];
275                let mut s = T::one();
276    
277                for &x in src_chunk.iter().skip(1) {
278                    if x <= m {
279                        s = s + (x - m).exp();
280                    } else {
281                        s = s * (m - x).exp() + T::one();
282                        m = x;
283                    }
284                }
285    
286                // ===== pass 2: 写最终结果 =====
287                for (i, &x) in src_chunk.iter().enumerate() {
288                    dst_chunk[i] = (x - m).exp() / s;
289                }
290            }
291        } else {
292            let input_layout = self.layout().narrow(reduce_dim, 0, 1)?;
293            let out_layout = Layout::contiguous(self.shape());
294            let out_layout_narrow = out_layout.narrow(reduce_dim, 0, 1)?;
295            let out_stride = out_layout.stride()[reduce_dim];
296    
297            for (src_offset, dst_offset) in
298                input_layout.storage_indices().zip(out_layout_narrow.storage_indices())
299            {
300                // ===== pass 1 =====
301                let mut m = src_data[src_offset];
302                let mut s = T::one();
303    
304                for i in 1..reduce_dim_size {
305                    let x = src_data[src_offset + i * reduce_dim_stride];
306                    if x <= m {
307                        s = s + (x - m).exp();
308                    } else {
309                        s = s * (m - x).exp() + T::one();
310                        m = x;
311                    }
312                }
313    
314                // ===== pass 2 =====
315                for i in 0..reduce_dim_size {
316                    let x = src_data[src_offset + i * reduce_dim_stride];
317                    dst[dst_offset + i * out_stride] = (x - m).exp() / s;
318                }
319            }
320        }
321    
322        let storage = Storage::new(dst);
323        let meta = T::AutogradMeta::on_softmax_op(self, reduce_dim);
324        Ok(Tensor::<T>::from_storage(storage, self.shape(), meta))
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use crate::{Tensor, Var};
331
332    #[test]
333    fn test_rms_norm_forward() -> crate::Result<()> {
334        let weight = Tensor::randn(0.0, 1.0, (64,))?;
335        const EPS: f64 = 1e-4;
336
337        let input = Tensor::randn(0.0, 1.0, (4, 10, 64))?;
338        println!("{}", input.is_contiguous());
339        let result1 = Tensor::rms_norm(&input, &weight, EPS)?;
340        let result_fused1 = Tensor::rms_norm_fused(&input, &weight, EPS)?;
341        assert!(result1.allclose(&result_fused1, 1e-5, 8e-8)?);
342
343        let input = input.transpose_last()?.copy()?.transpose_last()?;
344        println!("{}", input.is_contiguous());
345        let result2 = Tensor::rms_norm(&input, &weight, EPS)?;
346        let result_fused2 = Tensor::rms_norm_fused(&input, &weight, EPS)?;
347        assert!(result1.allclose(&result2, 1e-5, 8e-8)?);
348        assert!(result1.allclose(&result_fused2, 1e-5, 8e-8)?);
349
350        Ok(())
351    }
352
353    use std::time::Instant;
354
355    #[test]
356    fn test_rms_norm_backward() -> crate::Result<()> {
357        let input = Var::randn(0.0, 1.0, (1, 32, 128))?;
358        let weight = Var::randn(0.0, 1.0, (128,))?;
359        const EPS: f64 = 1e-4;
360    
361        {
362            let _ = Tensor::rms_norm(&input, &weight, EPS)?;
363            let _ = Tensor::rms_norm_fused(&input, &weight, EPS)?;
364        }
365    
366        // baseline
367        let start = Instant::now();
368        let result = Tensor::rms_norm(&input, &weight, EPS)?;
369        let forward_time_1 = start.elapsed();
370    
371        let start = Instant::now();
372        let grads1 = result.backward()?;
373        let backward_time_1 = start.elapsed();
374    
375        // fused
376        let start = Instant::now();
377        let result_fused = Tensor::rms_norm_fused(&input, &weight, EPS)?;
378        let forward_time_2 = start.elapsed();
379    
380        let start = Instant::now();
381        let grads2 = result_fused.backward()?;
382        let backward_time_2 = start.elapsed();
383    
384        assert!(result.allclose(&result_fused, 1e-5, 8e-8)?);
385        assert!(grads1[&input].allclose(&grads2[&input], 1e-5, 8e-8)?);
386        assert!(grads1[&weight].allclose(&grads2[&weight], 1e-5, 8e-8)?);
387    
388        println!("=== Forward ===");
389        println!("rms_norm       : {:?}", forward_time_1);
390        println!("rms_norm_fused : {:?}", forward_time_2);
391    
392        println!("=== Backward ===");
393        println!("rms_norm       : {:?}", backward_time_1);
394        println!("rms_norm_fused : {:?}", backward_time_2);
395    
396        println!("=== Speedup ===");
397        println!(
398            "forward speedup : {:.2}x",
399            forward_time_1.as_secs_f64() / forward_time_2.as_secs_f64()
400        );
401        println!(
402            "backward speedup: {:.2}x",
403            backward_time_1.as_secs_f64() / backward_time_2.as_secs_f64()
404        );
405    
406        Ok(())
407    }
408
409    #[test]
410    fn test_softmax_forward() -> crate::Result<()> {
411        let input = Tensor::randn(0.0, 1.0, (4, 10, 64))?;
412        println!("{}", input.is_contiguous());
413        let result1 = Tensor::softmax(&input, 2)?;
414        let result_fused1 = Tensor::softmax_fused(&input, 2)?;
415        assert!(result1.allclose(&result_fused1, 1e-5, 8e-8)?);
416
417        let input = input.transpose_last()?.copy()?.transpose_last()?;
418        println!("{}", input.is_contiguous());
419        let result2 = Tensor::softmax(&input, 2)?;
420        let result_fused2 = Tensor::softmax_fused(&input, 2)?;
421        assert!(result1.allclose(&result2, 1e-5, 8e-8)?);
422        assert!(result1.allclose(&result_fused2, 1e-5, 8e-8)?);
423
424        Ok(())
425    }
426
427    #[test]
428    fn test_softmax_forward_not_last_dim() -> crate::Result<()> {
429        let input = Tensor::randn(0.0, 1.0, (4, 10, 64))?;
430        println!("{}", input.is_contiguous());
431        let result1 = Tensor::softmax(&input, 1)?;
432        let result_fused1 = Tensor::softmax_fused(&input, 1)?;
433        assert!(result1.allclose(&result_fused1, 1e-5, 8e-8)?);
434
435        let input = input.transpose_last()?.copy()?.transpose_last()?;
436        println!("{}", input.is_contiguous());
437        let result2 = Tensor::softmax(&input, 1)?;
438        let result_fused2 = Tensor::softmax_fused(&input, 1)?;
439        assert!(result1.allclose(&result2, 1e-5, 8e-8)?);
440        assert!(result1.allclose(&result_fused2, 1e-5, 8e-8)?);
441
442        Ok(())
443    }
444
445
446    #[test]
447    fn test_softmax_backward() -> crate::Result<()> {
448        let shape = (8, 32, 128);
449        let dims_to_test = [2];
450    
451        for dim in dims_to_test {
452            println!("\n========================================");
453            println!("Testing Softmax along dim: {}", dim);
454            println!("========================================");
455    
456            let input = Var::randn(0.0, 1.0, shape)?;
457            {
458                let _ = input.softmax(dim)?;
459                let _ = input.softmax_fused(dim)?;
460            }
461    
462            // baseline
463            let start = Instant::now();
464            let result = input.softmax(dim)?;
465            let forward_time_1 = start.elapsed();
466    
467            let start = Instant::now();
468            let grads1 = result.backward()?;
469            let backward_time_1 = start.elapsed();
470    
471            // fused
472            let start = Instant::now();
473            let result_fused = input.softmax_fused(dim)?;
474            let forward_time_2 = start.elapsed();
475    
476            let start = Instant::now();
477            let grads2 = result_fused.backward()?;
478            let backward_time_2 = start.elapsed();
479    
480            assert!(
481                result.allclose(&result_fused, 1e-5, 8e-8)?,
482                "Forward mismatch at dim {}", dim
483            );
484            assert!(
485                grads1[&input].allclose(&grads2[&input], 1e-5, 8e-8)?,
486                "Backward mismatch at dim {}", dim
487            );
488    
489            println!("=== Forward ===");
490            println!("softmax       : {:?}", forward_time_1);
491            println!("softmax_fused : {:?}", forward_time_2);
492    
493            println!("=== Backward ===");
494            println!("softmax       : {:?}", backward_time_1);
495            println!("softmax_fused : {:?}", backward_time_2);
496    
497            println!("=== Speedup ===");
498            println!(
499                "forward speedup : {:.2}x",
500                forward_time_1.as_secs_f64() / forward_time_2.as_secs_f64()
501            );
502            println!(
503                "backward speedup: {:.2}x",
504                backward_time_1.as_secs_f64() / backward_time_2.as_secs_f64()
505            );
506        }
507    
508        Ok(())
509    }
510}