1use crate::{Tensor, AutogradMetaT, Dim, FloatDType, Layout, Storage, StorageIndices, D};
2use super::reduce::DimArray;
3
4impl<T: FloatDType> Tensor<T> {
8 pub fn rms_norm(input: &Tensor<T>, weight: &Tensor<T>, eps: T) -> crate::Result<Tensor<T>> {
12 let variance = input.sqr()?.mean_keepdim(D::Minus1)?;
14
15 let rms = (variance + eps).sqrt()?;
17
18 let input_normalized = input.broadcast_div(&rms)?;
20
21 let out = input_normalized.broadcast_mul(weight)?;
23
24 Ok(out)
25 }
26
27 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 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 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 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 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 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(); let s_v = (sum_dot * d_inv) / variance; 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 input_gard_row_data[i] += rsqrt_v * (wi * gi - xi * s_v);
164
165 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 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 assert!(input_grad.is_contiguous());
196 let mut dx_storage = input_grad.storage_write()?;
197 let dx_data = dx_storage.data_mut();
198
199 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 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 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 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)?; let diff = self.broadcast_sub(&max)?; let num = diff.exp()?; 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 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 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 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 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 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 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 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 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 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}