1use std::sync::Arc;
20use torsh_core::{
21 dtype::TensorElement,
22 error::{Result, TorshError},
23};
24
25use crate::memory_pool::global_acquire_uninit;
26
27#[cfg(feature = "simd")]
30mod simd_imports {
31 pub use scirs2_core::ndarray::Array1;
39}
40
41#[cfg(feature = "simd")]
42use simd_imports::*;
43
44#[cfg(feature = "parallel")]
46use scirs2_core::chunking::{
47 CacheAwareness, ChunkConfig, ChunkStrategy, ComputeIntensity, GpuChunkSettings, MemoryPattern,
48 NumaStrategy,
49};
50
51use crate::core_ops::{Operation, Tensor, UnaryKind};
63#[cfg(feature = "simd")]
64use crate::simd_ops_f32::BinaryF32Op;
65use crate::storage::TensorStorage;
66
67impl<T: TensorElement> Tensor<T> {
68 pub(crate) fn record_unary(&self, mut result: Self, kind: UnaryKind) -> Self {
74 if crate::should_record_grad(self.requires_grad) {
75 result.requires_grad = true;
76 result.operation = Operation::Unary {
77 input: Arc::new(self.clone()),
78 kind,
79 };
80 }
81 result
82 }
83
84 pub(crate) fn record_leaky_relu(&self, mut result: Self, negative_slope: T) -> Self {
90 if crate::should_record_grad(self.requires_grad) {
91 result.requires_grad = true;
92 result.operation = Operation::LeakyRelu {
93 input: Arc::new(self.clone()),
94 negative_slope,
95 };
96 }
97 result
98 }
99
100 pub(crate) fn record_extremum(&self, other: &Self, mut result: Self, is_maximum: bool) -> Self {
106 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
107 result.requires_grad = true;
108 let lhs = Arc::new(self.clone());
109 let rhs = Arc::new(other.clone());
110 result.operation = if is_maximum {
111 Operation::Maximum { lhs, rhs }
112 } else {
113 Operation::Minimum { lhs, rhs }
114 };
115 }
116 result
117 }
118
119 pub(crate) fn record_abs_if_real<R: TensorElement>(&self, result: Tensor<R>) -> Tensor<R> {
137 match (self as &dyn std::any::Any).downcast_ref::<Tensor<R>>() {
138 Some(real_self) => real_self.record_unary(result, UnaryKind::Abs),
139 None => result,
140 }
141 }
142}
143
144#[cfg(feature = "simd")]
150const SIMD_BINARY_THRESHOLD: usize = 1024;
151
152#[cfg(feature = "parallel")]
158const PARALLEL_ELEMENTWISE_THRESHOLD: usize = 1024;
159
160#[cfg(feature = "simd")]
166fn dispatch_f64_into(op: BinaryF32Op, a: &[f64], b: &[f64], out: &mut [f64]) {
167 use scirs2_core::simd_ops::SimdUnifiedOps;
168 match op {
169 BinaryF32Op::Add => <f64 as SimdUnifiedOps>::simd_add_into(a, b, out),
170 BinaryF32Op::Sub => <f64 as SimdUnifiedOps>::simd_sub_into(a, b, out),
171 BinaryF32Op::Mul => <f64 as SimdUnifiedOps>::simd_mul_into(a, b, out),
172 BinaryF32Op::Div => <f64 as SimdUnifiedOps>::simd_div_into(a, b, out),
173 }
174}
175
176#[cfg(feature = "simd")]
178pub(crate) mod adaptive_simd {
179 use super::*;
180 use scirs2_core::ndarray::ArrayView1;
181
182 pub fn adaptive_simd_relu_f32(input: &ArrayView1<f32>) -> Array1<f32> {
185 scirs2_core::simd::activation::simd_relu_f32(input)
186 }
187
188 pub fn adaptive_simd_sigmoid_f32(input: &ArrayView1<f32>) -> Array1<f32> {
191 scirs2_core::simd::transcendental::simd_sigmoid_f32(input)
192 }
193
194 }
206
207#[cfg(feature = "simd")]
208#[cfg(feature = "parallel")]
210mod intelligent_chunking {
211 use super::*;
212
213 #[derive(Debug, Clone, Copy)]
215 pub enum TensorOpType {
216 ElementWise,
218 Activation,
220 }
221
222 pub fn create_optimal_chunk_config(
226 tensor_size: usize,
227 op_type: TensorOpType,
228 _device: torsh_core::device::DeviceType,
229 is_gpu_available: bool,
230 ) -> ChunkConfig {
231 match op_type {
232 TensorOpType::ElementWise => ChunkConfig {
233 strategy: if tensor_size > 100_000 {
234 ChunkStrategy::MemoryOptimized
235 } else {
236 ChunkStrategy::CacheOptimized
237 },
238 min_chunk_size: 64,
239 max_chunk_size: 8192,
240 prefer_work_stealing: true,
241 memory_pattern: MemoryPattern::Sequential,
242 compute_intensity: ComputeIntensity::MemoryBound,
243 enable_monitoring: false,
244 load_balance_factor: 0.1,
245 cache_awareness: CacheAwareness::L2,
246 numa_strategy: NumaStrategy::LocalPreferred,
247 gpu_settings: if is_gpu_available {
248 Some(GpuChunkSettings::default())
249 } else {
250 None
251 },
252 },
253
254 TensorOpType::Activation => ChunkConfig {
255 strategy: ChunkStrategy::CacheOptimized,
256 min_chunk_size: 64,
257 max_chunk_size: 4096,
258 prefer_work_stealing: true,
259 memory_pattern: MemoryPattern::Sequential,
260 compute_intensity: ComputeIntensity::ComputeIntensive,
261 enable_monitoring: false,
262 load_balance_factor: 0.1,
263 cache_awareness: CacheAwareness::L1,
264 numa_strategy: NumaStrategy::LocalPreferred,
265 gpu_settings: if is_gpu_available {
266 Some(GpuChunkSettings {
267 gpu_memory_ratio: 0.7,
268 gpu_min_chunk: 2048,
269 overlap_compute: true,
270 gpu_bandwidth: None, transfer_bandwidth: None, })
273 } else {
274 None
275 },
276 },
277 }
278 }
279
280 pub fn intelligent_parallel_process<T, F, R>(
282 data: Vec<T>,
283 op_type: TensorOpType,
284 device: torsh_core::device::DeviceType,
285 operation: F,
286 ) -> Vec<R>
287 where
288 T: Send + Sync,
289 R: Send + Sync,
290 F: Fn(T) -> R + Send + Sync,
291 {
292 let is_gpu_available = matches!(
293 device,
294 torsh_core::device::DeviceType::Cuda(_)
295 | torsh_core::device::DeviceType::Metal(_)
296 | torsh_core::device::DeviceType::Wgpu(_)
297 );
298
299 let _chunk_config =
300 create_optimal_chunk_config(data.len(), op_type, device, is_gpu_available);
301
302 #[cfg(feature = "parallel")]
304 {
305 use scirs2_core::parallel_ops::*;
306 data.into_par_iter().map(operation).collect()
307 }
308 #[cfg(not(feature = "parallel"))]
309 {
310 data.into_iter().map(operation).collect()
311 }
312 }
313}
314
315#[cfg(feature = "parallel")]
316use intelligent_chunking::*;
317
318fn can_broadcast(shape1: &[usize], shape2: &[usize]) -> bool {
320 let max_dims = shape1.len().max(shape2.len());
321
322 for i in 0..max_dims {
323 let dim1 = if i < shape1.len() {
324 shape1[shape1.len() - 1 - i]
325 } else {
326 1
327 };
328 let dim2 = if i < shape2.len() {
329 shape2[shape2.len() - 1 - i]
330 } else {
331 1
332 };
333
334 if dim1 != dim2 && dim1 != 1 && dim2 != 1 {
335 return false;
336 }
337 }
338 true
339}
340
341fn compute_broadcast_shape(shape1: &[usize], shape2: &[usize]) -> Result<Vec<usize>> {
343 let max_dims = shape1.len().max(shape2.len());
344 let mut result = Vec::with_capacity(max_dims);
345
346 for i in 0..max_dims {
347 let dim1 = if i < shape1.len() {
348 shape1[shape1.len() - 1 - i]
349 } else {
350 1
351 };
352 let dim2 = if i < shape2.len() {
353 shape2[shape2.len() - 1 - i]
354 } else {
355 1
356 };
357
358 if dim1 == dim2 {
359 result.push(dim1);
360 } else if dim1 == 1 {
361 result.push(dim2);
362 } else if dim2 == 1 {
363 result.push(dim1);
364 } else {
365 return Err(TorshError::ShapeMismatch {
366 expected: shape1.to_vec(),
367 got: shape2.to_vec(),
368 });
369 }
370 }
371
372 result.reverse();
373 Ok(result)
374}
375
376fn compute_broadcast_index(
378 flat_idx: usize,
379 broadcast_shape: &[usize],
380 original_shape: &[usize],
381) -> usize {
382 let mut result = 0;
383 let mut remaining = flat_idx;
384
385 let dims_diff = broadcast_shape.len() - original_shape.len();
386
387 for (i, &broadcast_dim) in broadcast_shape.iter().enumerate() {
388 let stride = broadcast_shape[i + 1..].iter().product::<usize>().max(1);
390 let coord = remaining / stride;
391 remaining %= stride;
392
393 debug_assert!(
395 coord < broadcast_dim,
396 "Coordinate {} out of bounds for dimension {} of size {}",
397 coord,
398 i,
399 broadcast_dim
400 );
401
402 if i >= dims_diff {
403 let original_dim = original_shape[i - dims_diff];
404 let adjusted_coord = if original_dim == 1 { 0 } else { coord };
405 result = result * original_dim + adjusted_coord;
406 }
407 }
408
409 result
410}
411
412impl<T: TensorElement + Copy> Tensor<T> {
413 pub(crate) fn shares_storage_buffer(&self, other: &Self) -> bool {
424 match (&self.storage, &other.storage) {
425 (TensorStorage::InMemory(a), TensorStorage::InMemory(b)) => Arc::ptr_eq(a, b),
426 (TensorStorage::MemoryMapped(a), TensorStorage::MemoryMapped(b)) => Arc::ptr_eq(a, b),
427 #[cfg(feature = "simd")]
428 (TensorStorage::Aligned(a), TensorStorage::Aligned(b)) => Arc::ptr_eq(a, b),
429 #[cfg(feature = "simd")]
430 (TensorStorage::SimdOptimized(a), TensorStorage::SimdOptimized(b)) => Arc::ptr_eq(a, b),
431 #[cfg(feature = "gpu")]
432 (TensorStorage::Device { buffer: a, .. }, TensorStorage::Device { buffer: b, .. }) => {
433 Arc::ptr_eq(a, b)
434 }
435 _ => false,
436 }
437 }
438
439 fn storage_addr(&self) -> usize {
444 match &self.storage {
445 TensorStorage::InMemory(data) => Arc::as_ptr(data) as usize,
446 TensorStorage::MemoryMapped(storage) => Arc::as_ptr(storage) as usize,
447 #[cfg(feature = "simd")]
448 TensorStorage::Aligned(data) => Arc::as_ptr(data) as usize,
449 #[cfg(feature = "simd")]
450 TensorStorage::SimdOptimized(storage) => Arc::as_ptr(storage) as usize,
451 #[cfg(feature = "gpu")]
452 TensorStorage::Device { buffer, .. } => Arc::as_ptr(buffer) as usize,
453 }
454 }
455
456 pub(crate) fn with_operand_slices<R, F>(&self, other: &Self, f: F) -> Result<R>
470 where
471 F: FnOnce(&[T], &[T]) -> Result<R>,
472 {
473 if self.shares_storage_buffer(other) {
474 let other_data = other.to_vec()?;
476 return self.with_contiguous_data(|lhs| f(lhs, &other_data));
477 }
478 if self.storage_addr() <= other.storage_addr() {
479 self.with_contiguous_data(|lhs| other.with_contiguous_data(|rhs| f(lhs, rhs)))
480 } else {
481 other.with_contiguous_data(|rhs| self.with_contiguous_data(|lhs| f(lhs, rhs)))
482 }
483 }
484
485 #[cfg(feature = "simd")]
496 fn try_simd_binary(&self, other: &Self, op: BinaryF32Op) -> Result<Option<Self>> {
497 let n = self.numel();
498 if n < SIMD_BINARY_THRESHOLD || self.shape() != other.shape() {
499 return Ok(None);
500 }
501 let is_f32 = std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>();
502 let is_f64 = std::any::TypeId::of::<T>() == std::any::TypeId::of::<f64>();
503 if !is_f32 && !is_f64 {
504 return Ok(None);
505 }
506
507 let result_data = self.with_operand_slices(other, |a, b| {
508 if a.len() != n || b.len() != n {
509 return Err(TorshError::ShapeMismatch {
510 expected: vec![n],
511 got: vec![b.len()],
512 });
513 }
514 let mut buf = global_acquire_uninit::<T>(n);
515 {
516 let uninit = &mut buf.as_uninit_slice_mut()[..n];
517 let out: &mut [T] =
523 unsafe { std::slice::from_raw_parts_mut(uninit.as_mut_ptr().cast::<T>(), n) };
524 if is_f32 {
525 unsafe {
528 op.dispatch_into(
529 std::slice::from_raw_parts(a.as_ptr().cast::<f32>(), n),
530 std::slice::from_raw_parts(b.as_ptr().cast::<f32>(), n),
531 std::slice::from_raw_parts_mut(out.as_mut_ptr().cast::<f32>(), n),
532 );
533 }
534 } else {
535 unsafe {
537 dispatch_f64_into(
538 op,
539 std::slice::from_raw_parts(a.as_ptr().cast::<f64>(), n),
540 std::slice::from_raw_parts(b.as_ptr().cast::<f64>(), n),
541 std::slice::from_raw_parts_mut(out.as_mut_ptr().cast::<f64>(), n),
542 );
543 }
544 }
545 }
546 Ok(buf.into_vec(n))
547 })?;
548
549 let result = Self::from_data(result_data, self.shape().dims().to_vec(), self.device)?;
550 Ok(Some(result))
551 }
552
553 pub fn add_scalar_(&mut self, scalar: T) -> Result<()>
555 where
556 T: Copy + std::ops::Add<Output = T>,
557 {
558 self.make_unique()?;
560 self.apply_(|x| x + scalar)
561 }
562
563 pub fn add_scalar(&self, scalar: T) -> Result<Self>
569 where
570 T: Copy + std::ops::Add<Output = T>,
571 {
572 let mut result = self.map(|x| x + scalar)?;
573 if crate::should_record_grad(self.requires_grad) {
574 result.requires_grad = true;
575 result.operation = Operation::AddScalar {
576 input: Arc::new(self.clone()),
577 scalar,
578 };
579 }
580 Ok(result)
581 }
582
583 pub fn sub_scalar_(&mut self, scalar: T) -> Result<()>
585 where
586 T: Copy + std::ops::Sub<Output = T>,
587 {
588 self.make_unique()?;
589 self.apply_(|x| x - scalar)
590 }
591
592 pub fn sub_scalar(&self, scalar: T) -> Result<Self>
598 where
599 T: Copy + std::ops::Sub<Output = T>,
600 {
601 let mut result = self.map(|x| x - scalar)?;
602 if crate::should_record_grad(self.requires_grad) {
603 result.requires_grad = true;
604 result.operation = Operation::AddScalar {
605 input: Arc::new(self.clone()),
606 scalar: <T as TensorElement>::zero() - scalar,
607 };
608 }
609 Ok(result)
610 }
611
612 pub fn mul_scalar_(&mut self, scalar: T) -> Result<()>
614 where
615 T: Copy + std::ops::Mul<Output = T>,
616 {
617 self.make_unique()?;
619 self.apply_(|x| x * scalar)
620 }
621
622 pub fn mul_scalar(&self, scalar: T) -> Result<Self>
628 where
629 T: Copy + std::ops::Mul<Output = T>,
630 {
631 let mut result = self.map(|x| x * scalar)?;
632 if crate::should_record_grad(self.requires_grad) {
633 result.requires_grad = true;
634 result.operation = Operation::MulScalar {
635 input: Arc::new(self.clone()),
636 scalar,
637 };
638 }
639 Ok(result)
640 }
641
642 pub fn div_scalar_(&mut self, scalar: T) -> Result<()>
644 where
645 T: Copy + std::ops::Div<Output = T>,
646 {
647 self.make_unique()?;
648 self.apply_(|x| x / scalar)
649 }
650
651 pub fn div_scalar(&self, scalar: T) -> Result<Self>
656 where
657 T: Copy + std::ops::Div<Output = T>,
658 {
659 let mut result = self.map(|x| x / scalar)?;
660 if crate::should_record_grad(self.requires_grad) {
661 result.requires_grad = true;
662 result.operation = Operation::DivScalar {
663 input: Arc::new(self.clone()),
664 scalar,
665 };
666 }
667 Ok(result)
668 }
669
670 pub fn add(&self, other: &Self) -> Result<Self>
672 where
673 T: std::ops::Add<Output = T>,
674 {
675 if self.shape() != other.shape() {
677 return self.broadcast_add(other);
678 }
679
680 #[cfg(feature = "simd")]
683 if let Some(mut result) = self.try_simd_binary(other, BinaryF32Op::Add)? {
684 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
685 result.requires_grad = true;
686 result.operation = Operation::Add {
687 lhs: Arc::new(self.clone()),
688 rhs: Arc::new(other.clone()),
689 };
690 }
691 return Ok(result);
692 }
693
694 let mut result = self.elementwise_operation(other, |a, b| a + b)?;
696
697 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
699 result.requires_grad = true;
700 result.operation = Operation::Add {
701 lhs: Arc::new(self.clone()),
702 rhs: Arc::new(other.clone()),
703 };
704 }
705
706 Ok(result)
707 }
708
709 fn broadcast_add(&self, other: &Self) -> Result<Self>
711 where
712 T: std::ops::Add<Output = T>,
713 {
714 let self_shape_binding = self.shape();
716 let other_shape_binding = other.shape();
717 let self_shape = self_shape_binding.dims();
718 let other_shape = other_shape_binding.dims();
719
720 if !can_broadcast(self_shape, other_shape) {
722 return Err(TorshError::ShapeMismatch {
723 expected: self_shape.to_vec(),
724 got: other_shape.to_vec(),
725 });
726 }
727
728 let broadcast_shape = compute_broadcast_shape(self_shape, other_shape)?;
730
731 let total_elems: usize = broadcast_shape.iter().product();
733 let result_data = self.with_operand_slices(other, |self_data, other_data| {
734 let mut buf = global_acquire_uninit::<T>(total_elems);
735 let uninit = &mut buf.as_uninit_slice_mut()[..total_elems];
736 let mut count = 0;
737
738 for i in 0..total_elems {
739 let self_idx = compute_broadcast_index(i, &broadcast_shape, self_shape);
740 let other_idx = compute_broadcast_index(i, &broadcast_shape, other_shape);
741
742 let self_val = *self_data
743 .get(self_idx)
744 .ok_or_else(|| TorshError::IndexError {
745 index: self_idx,
746 size: self_data.len(),
747 })?;
748 let other_val =
749 *other_data
750 .get(other_idx)
751 .ok_or_else(|| TorshError::IndexError {
752 index: other_idx,
753 size: other_data.len(),
754 })?;
755 uninit[count].write(self_val + other_val);
756 count += 1;
757 }
758
759 Ok(buf.into_vec(count))
760 })?;
761 let mut result = Self::from_data(result_data, broadcast_shape, self.device)?;
762
763 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
765 result.requires_grad = true;
766 result.operation = Operation::Add {
767 lhs: Arc::new(self.clone()),
768 rhs: Arc::new(other.clone()),
769 };
770 }
771
772 Ok(result)
773 }
774
775 pub fn sub(&self, other: &Self) -> Result<Self>
777 where
778 T: std::ops::Sub<Output = T>,
779 {
780 #[cfg(feature = "simd")]
783 if let Some(mut result) = self.try_simd_binary(other, BinaryF32Op::Sub)? {
784 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
785 result.requires_grad = true;
786 result.operation = crate::Operation::Sub {
787 lhs: Arc::new(self.clone()),
788 rhs: Arc::new(other.clone()),
789 };
790 }
791 return Ok(result);
792 }
793
794 let mut result = self.elementwise_operation(other, |a, b| a - b)?;
795
796 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
798 result.requires_grad = true;
799 result.operation = crate::Operation::Sub {
800 lhs: Arc::new(self.clone()),
801 rhs: Arc::new(other.clone()),
802 };
803 }
804
805 Ok(result)
806 }
807
808 pub fn mul(&self, other: &Self) -> Result<Self>
815 where
816 T: std::ops::Mul<Output = T>,
817 {
818 #[cfg(feature = "simd")]
821 if let Some(mut result) = self.try_simd_binary(other, BinaryF32Op::Mul)? {
822 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
823 result.requires_grad = true;
824 result.operation = crate::Operation::Mul {
825 lhs: Arc::new(self.clone()),
826 rhs: Arc::new(other.clone()),
827 };
828 }
829 return Ok(result);
830 }
831
832 let mut result = self.elementwise_operation(other, |a, b| a * b)?;
833
834 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
836 result.requires_grad = true;
837 result.operation = crate::Operation::Mul {
838 lhs: Arc::new(self.clone()),
839 rhs: Arc::new(other.clone()),
840 };
841 }
842
843 Ok(result)
844 }
845
846 pub fn div(&self, other: &Self) -> Result<Self>
853 where
854 T: std::ops::Div<Output = T>,
855 {
856 #[cfg(feature = "simd")]
859 if let Some(mut result) = self.try_simd_binary(other, BinaryF32Op::Div)? {
860 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
861 result.requires_grad = true;
862 result.operation = crate::Operation::Div {
863 lhs: Arc::new(self.clone()),
864 rhs: Arc::new(other.clone()),
865 };
866 }
867 return Ok(result);
868 }
869
870 let mut result = self.elementwise_operation(other, |a, b| a / b)?;
871
872 if crate::should_record_grad(self.requires_grad || other.requires_grad) {
874 result.requires_grad = true;
875 result.operation = crate::Operation::Div {
876 lhs: Arc::new(self.clone()),
877 rhs: Arc::new(other.clone()),
878 };
879 }
880
881 Ok(result)
882 }
883
884 fn broadcast_binary_op<F>(&self, other: &Self, op: F) -> Result<Self>
886 where
887 F: Fn(T, T) -> T + Send + Sync,
888 {
889 use crate::broadcast::BroadcastOps;
890
891 let self_shape_binding = self.shape();
892 let self_shape = self_shape_binding.dims();
893 let other_shape_binding = other.shape();
894 let other_shape = other_shape_binding.dims();
895
896 let broadcast_shape = BroadcastOps::compute_broadcast_shape(self_shape, other_shape)?;
898
899 let total_elements = broadcast_shape.iter().product::<usize>();
900 let result_data = self.with_operand_slices(other, |self_data, other_data| {
901 let mut buf = global_acquire_uninit::<T>(total_elements);
902 let uninit = &mut buf.as_uninit_slice_mut()[..total_elements];
903 let mut count = 0;
904
905 let mut indices = vec![0; broadcast_shape.len()];
907 for _ in 0..total_elements {
908 let self_idx =
910 self.compute_broadcast_index(&indices, self_shape, &broadcast_shape)?;
911 let other_idx =
912 other.compute_broadcast_index(&indices, other_shape, &broadcast_shape)?;
913
914 let lhs = *self_data
915 .get(self_idx)
916 .ok_or_else(|| TorshError::IndexError {
917 index: self_idx,
918 size: self_data.len(),
919 })?;
920 let rhs = *other_data
921 .get(other_idx)
922 .ok_or_else(|| TorshError::IndexError {
923 index: other_idx,
924 size: other_data.len(),
925 })?;
926 uninit[count].write(op(lhs, rhs));
927 count += 1;
928
929 Self::increment_indices(&mut indices, &broadcast_shape);
931 }
932
933 Ok(buf.into_vec(count))
934 })?;
935 Self::from_data(result_data, broadcast_shape, self.device)
936 }
937
938 fn increment_indices(indices: &mut [usize], shape: &[usize]) {
940 for i in (0..indices.len()).rev() {
941 indices[i] += 1;
942 if indices[i] < shape[i] {
943 break;
944 }
945 indices[i] = 0;
946 }
947 }
948
949 fn compute_broadcast_index(
951 &self,
952 broadcast_indices: &[usize],
953 original_shape: &[usize],
954 broadcast_shape: &[usize],
955 ) -> Result<usize> {
956 let ndim_diff = broadcast_shape.len() - original_shape.len();
957 let mut flat_index = 0;
958 let mut stride = 1;
959
960 for i in (0..original_shape.len()).rev() {
961 let broadcast_idx = broadcast_indices[ndim_diff + i];
962 let original_size = original_shape[i];
963
964 let actual_idx = if original_size == 1 { 0 } else { broadcast_idx };
966
967 flat_index += actual_idx * stride;
968 stride *= original_size;
969 }
970
971 Ok(flat_index)
972 }
973
974 fn elementwise_operation<F>(&self, other: &Self, op: F) -> Result<Self>
985 where
986 F: Fn(T, T) -> T + Send + Sync,
987 {
988 if self.shape() != other.shape() {
990 return self.broadcast_binary_op(other, op);
991 }
992
993 let n = self.numel();
994 let device = self.device;
995 let result_data = self.with_operand_slices(other, |a, b| {
996 if a.len() != n || b.len() != n {
997 return Err(TorshError::ShapeMismatch {
998 expected: vec![n],
999 got: vec![b.len()],
1000 });
1001 }
1002
1003 let mut buf = global_acquire_uninit::<T>(n);
1004 {
1005 let uninit = &mut buf.as_uninit_slice_mut()[..n];
1006
1007 #[cfg(feature = "parallel")]
1010 {
1011 if n >= PARALLEL_ELEMENTWISE_THRESHOLD {
1012 use scirs2_core::parallel_ops::*;
1013 let config = create_optimal_chunk_config(
1014 n,
1015 TensorOpType::ElementWise,
1016 device,
1017 false,
1018 );
1019 let chunk = config.max_chunk_size.max(config.min_chunk_size).max(1);
1020 uninit
1021 .par_chunks_mut(chunk)
1022 .zip(a.par_chunks(chunk))
1023 .zip(b.par_chunks(chunk))
1024 .for_each(|((out_chunk, a_chunk), b_chunk)| {
1025 for (slot, (&x, &y)) in
1026 out_chunk.iter_mut().zip(a_chunk.iter().zip(b_chunk.iter()))
1027 {
1028 slot.write(op(x, y));
1029 }
1030 });
1031 return Ok(buf.into_vec(n));
1032 }
1033 }
1034
1035 for (slot, (&x, &y)) in uninit.iter_mut().zip(a.iter().zip(b.iter())) {
1036 slot.write(op(x, y));
1037 }
1038 }
1039 Ok(buf.into_vec(n))
1040 })?;
1041
1042 Self::from_data(result_data, self.shape().dims().to_vec(), device)
1043 }
1044}
1045
1046impl<T: TensorElement + Copy> Tensor<T> {
1051 pub fn add_(&mut self, other: &Self) -> Result<&mut Self>
1065 where
1066 T: std::ops::Add<Output = T>,
1067 {
1068 self.inplace_binary_op(
1069 other,
1070 "add_",
1071 crate::simd_ops_f32::add_assign_f32,
1072 |a, b| a + b,
1073 )
1074 }
1075
1076 pub fn sub_(&mut self, other: &Self) -> Result<&mut Self>
1081 where
1082 T: std::ops::Sub<Output = T>,
1083 {
1084 self.inplace_binary_op(
1085 other,
1086 "sub_",
1087 crate::simd_ops_f32::sub_assign_f32,
1088 |a, b| a - b,
1089 )
1090 }
1091
1092 pub fn mul_(&mut self, other: &Self) -> Result<&mut Self>
1097 where
1098 T: std::ops::Mul<Output = T>,
1099 {
1100 self.inplace_binary_op(
1101 other,
1102 "mul_",
1103 crate::simd_ops_f32::mul_assign_f32,
1104 |a, b| a * b,
1105 )
1106 }
1107
1108 pub fn div_(&mut self, other: &Self) -> Result<&mut Self>
1113 where
1114 T: std::ops::Div<Output = T>,
1115 {
1116 self.inplace_binary_op(
1117 other,
1118 "div_",
1119 crate::simd_ops_f32::div_assign_f32,
1120 |a, b| a / b,
1121 )
1122 }
1123
1124 fn inplace_binary_op<S, F>(
1128 &mut self,
1129 other: &Self,
1130 op_name: &str,
1131 simd_f32: S,
1132 scalar_op: F,
1133 ) -> Result<&mut Self>
1134 where
1135 S: Fn(&mut [f32], &[f32]),
1136 F: Fn(T, T) -> T,
1137 {
1138 if self.requires_grad {
1139 return Err(TorshError::InvalidArgument(format!(
1140 "In-place operation `{op_name}` on tensor that requires grad is not allowed"
1141 )));
1142 }
1143
1144 if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>()
1146 && self.shape() == other.shape()
1147 && self.numel() >= 1024
1148 {
1149 self.make_unique()?;
1150 let other_data = other.data()?;
1151 self.storage.with_slice_mut(|out_t: &mut [T]| {
1152 let out_f32: &mut [f32] = unsafe {
1155 std::slice::from_raw_parts_mut(out_t.as_mut_ptr() as *mut f32, out_t.len())
1156 };
1157 let rhs_f32: &[f32] = unsafe {
1158 std::slice::from_raw_parts(other_data.as_ptr() as *const f32, other_data.len())
1159 };
1160 simd_f32(out_f32, rhs_f32);
1161 Ok(())
1162 })?;
1163 return Ok(self);
1164 }
1165
1166 let self_dims_vec = self.shape().dims().to_vec();
1168 let other_dims_vec = other.shape().dims().to_vec();
1169 let other_data = other.data()?;
1170
1171 if self_dims_vec == other_dims_vec {
1172 self.make_unique()?;
1174 self.storage.with_slice_mut(|slice: &mut [T]| {
1175 debug_assert_eq!(slice.len(), other_data.len());
1176 for (dst, src) in slice.iter_mut().zip(other_data.iter()) {
1177 *dst = scalar_op(*dst, *src);
1178 }
1179 Ok(())
1180 })?;
1181 return Ok(self);
1182 }
1183
1184 use crate::broadcast::BroadcastOps;
1187 let broadcast_shape =
1188 BroadcastOps::compute_broadcast_shape(&self_dims_vec, &other_dims_vec)?;
1189 if broadcast_shape != self_dims_vec {
1190 return Err(TorshError::ShapeMismatch {
1191 expected: self_dims_vec,
1192 got: broadcast_shape,
1193 });
1194 }
1195
1196 self.make_unique()?;
1197 let total = self_dims_vec.iter().product::<usize>();
1198 let self_dims = self_dims_vec.as_slice();
1199 let other_dims = other_dims_vec.as_slice();
1200 self.storage.with_slice_mut(|slice: &mut [T]| {
1201 for flat_idx in 0..total {
1202 let multi_index = BroadcastOps::flat_to_multi_index(flat_idx, self_dims);
1203 let other_idx =
1204 BroadcastOps::compute_broadcast_index(&multi_index, other_dims, self_dims)?;
1205 slice[flat_idx] = scalar_op(slice[flat_idx], other_data[other_idx]);
1206 }
1207 Ok(())
1208 })?;
1209
1210 Ok(self)
1211 }
1212}
1213
1214impl<T: TensorElement + Copy> Tensor<T> {
1216 pub fn add_op(&self, other: &Self) -> Result<Self>
1218 where
1219 T: std::ops::Add<Output = T>,
1220 {
1221 self.add(other)
1222 }
1223
1224 pub fn mul_op(&self, other: &Self) -> Result<Self>
1226 where
1227 T: std::ops::Mul<Output = T>,
1228 {
1229 self.mul(other)
1230 }
1231
1232 pub fn sigmoid(&self) -> Result<Self>
1234 where
1235 T: torsh_core::dtype::FloatElement,
1236 {
1237 let result = self.sigmoid_forward()?;
1238 Ok(self.record_unary(result, UnaryKind::Sigmoid))
1239 }
1240
1241 fn sigmoid_forward(&self) -> Result<Self>
1244 where
1245 T: torsh_core::dtype::FloatElement,
1246 {
1247 #[cfg(feature = "gpu")]
1250 if let Some(result) =
1251 crate::gpu_dispatch::try_unary_f32(self, crate::gpu_dispatch::UnaryOp::Sigmoid)
1252 {
1253 return Ok(result);
1254 }
1255
1256 #[cfg(feature = "simd")]
1258 {
1259 if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() && self.numel() > 1000 {
1260 return self.simd_sigmoid_f32();
1261 }
1262 }
1263
1264 #[cfg(feature = "parallel")]
1266 {
1267 if self.numel() > 100 {
1268 let one = <T as scirs2_core::numeric::One>::one();
1269 return self.parallel_map(|x| {
1270 one / (one + (-x).exp())
1272 });
1273 }
1274 }
1275
1276 let one = <T as scirs2_core::numeric::One>::one();
1278 let neg_self = self.neg()?;
1279 let exp_neg = neg_self.exp()?;
1280 let one_plus_exp = exp_neg.add_scalar(one)?;
1281 let ones = Self::ones(self.shape().dims(), self.device)?;
1282 ones.div(&one_plus_exp)
1283 }
1284
1285 #[cfg(feature = "simd")]
1287 fn simd_sigmoid_f32(&self) -> Result<Self> {
1288 self.simd_activation_f32(adaptive_simd::adaptive_simd_sigmoid_f32)
1289 }
1290
1291 #[cfg(feature = "simd")]
1302 fn simd_activation_f32<K>(&self, kernel: K) -> Result<Self>
1303 where
1304 K: Fn(&scirs2_core::ndarray::ArrayView1<f32>) -> Array1<f32>,
1305 {
1306 use scirs2_core::ndarray::ArrayView1;
1307
1308 let numel = self.numel();
1309 let result_data: Vec<T> = self.with_contiguous_data(|data| {
1310 let data_f32: &[f32] =
1313 unsafe { std::slice::from_raw_parts(data.as_ptr().cast::<f32>(), numel) };
1314 let result_array = kernel(&ArrayView1::from(data_f32));
1315 let (values, _offset) = result_array.into_raw_vec_and_offset();
1316 Ok(unsafe {
1320 let mut values = std::mem::ManuallyDrop::new(values);
1321 Vec::from_raw_parts(
1322 values.as_mut_ptr().cast::<T>(),
1323 values.len(),
1324 values.capacity(),
1325 )
1326 })
1327 })?;
1328
1329 Self::from_data(result_data, self.shape().dims().to_vec(), self.device)
1330 }
1331
1332 pub fn relu(&self) -> Result<Self>
1334 where
1335 T: std::cmp::PartialOrd + scirs2_core::numeric::Zero,
1336 {
1337 let result = self.relu_forward()?;
1338 Ok(self.record_unary(result, UnaryKind::Relu))
1339 }
1340
1341 fn relu_forward(&self) -> Result<Self>
1344 where
1345 T: std::cmp::PartialOrd + scirs2_core::numeric::Zero,
1346 {
1347 #[cfg(feature = "gpu")]
1350 if let Some(result) =
1351 crate::gpu_dispatch::try_unary_f32(self, crate::gpu_dispatch::UnaryOp::Relu)
1352 {
1353 return Ok(result);
1354 }
1355
1356 let zero = <T as scirs2_core::numeric::Zero>::zero();
1357
1358 #[cfg(feature = "simd")]
1360 {
1361 if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() && self.numel() > 1000 {
1362 return self.simd_relu_f32();
1363 }
1364 }
1365
1366 #[cfg(feature = "parallel")]
1368 {
1369 if self.numel() > 100 {
1370 return self.parallel_map(|x| if x > zero { x } else { zero });
1371 }
1372 }
1373
1374 self.map(|x| if x > zero { x } else { zero })
1376 }
1377
1378 #[cfg(feature = "simd")]
1380 fn simd_relu_f32(&self) -> Result<Self> {
1381 self.simd_activation_f32(adaptive_simd::adaptive_simd_relu_f32)
1382 }
1383
1384 #[cfg(feature = "parallel")]
1386 fn parallel_map<F>(&self, op: F) -> Result<Self>
1387 where
1388 F: Fn(T) -> T + Send + Sync,
1389 {
1390 let data = self.data()?;
1391
1392 let result_data = intelligent_parallel_process(
1394 data.iter().copied().collect::<Vec<_>>(),
1395 TensorOpType::Activation, self.device.clone(),
1397 op,
1398 );
1399
1400 Self::from_data(result_data, self.shape().dims().to_vec(), self.device)
1401 }
1402
1403 pub fn minimum(&self, other: &Self) -> Result<Self>
1410 where
1411 T: std::cmp::PartialOrd,
1412 {
1413 let result = self.elementwise_operation(other, |a, b| if a < b { a } else { b })?;
1414 Ok(self.record_extremum(other, result, false))
1415 }
1416
1417 pub fn maximum(&self, other: &Self) -> Result<Self>
1423 where
1424 T: std::cmp::PartialOrd,
1425 {
1426 let result = self.elementwise_operation(other, |a, b| if a > b { a } else { b })?;
1427 Ok(self.record_extremum(other, result, true))
1428 }
1429
1430 pub fn clamp(&self, min: T, max: T) -> Result<Self>
1436 where
1437 T: std::cmp::PartialOrd + Copy,
1438 {
1439 self.clamp_bounds(Some(min), Some(max))
1440 }
1441
1442 pub fn clamp_min(&self, min: T) -> Result<Self>
1446 where
1447 T: std::cmp::PartialOrd + Copy,
1448 {
1449 self.clamp_bounds(Some(min), None)
1450 }
1451
1452 pub fn clamp_max(&self, max: T) -> Result<Self>
1456 where
1457 T: std::cmp::PartialOrd + Copy,
1458 {
1459 self.clamp_bounds(None, Some(max))
1460 }
1461
1462 fn clamp_bounds(&self, min: Option<T>, max: Option<T>) -> Result<Self>
1466 where
1467 T: std::cmp::PartialOrd + Copy,
1468 {
1469 let data = self.to_vec()?;
1470 let clamped_data: Vec<T> = data
1475 .iter()
1476 .map(|&x| match (min, max) {
1477 (Some(lower), _) if x < lower => lower,
1478 (_, Some(upper)) if x > upper => upper,
1479 _ => x,
1480 })
1481 .collect();
1482
1483 let mut result = Self::from_data(
1484 clamped_data,
1485 self.shape().dims().to_vec(),
1486 self.device.clone(),
1487 )?;
1488
1489 if crate::should_record_grad(self.requires_grad) {
1490 result.requires_grad = true;
1491 result.operation = Operation::ClampBounds {
1492 input: Arc::new(self.clone()),
1493 min,
1494 max,
1495 };
1496 }
1497
1498 Ok(result)
1499 }
1500
1501 pub fn dot(&self, other: &Self) -> Result<Self>
1505 where
1506 T: std::ops::Mul<Output = T> + std::ops::Add<Output = T> + scirs2_core::numeric::Zero,
1507 {
1508 let elementwise = self.mul(other)?;
1510 elementwise.sum()
1511 }
1512}
1513
1514impl<T: TensorElement + Copy + scirs2_core::numeric::FromPrimitive> Tensor<T> {
1516 pub fn add_scirs2(&self, other: &Self) -> Result<Self>
1518 where
1519 T: std::ops::Add<Output = T> + scirs2_core::numeric::Float,
1520 {
1521 self.add(other)
1524 }
1525
1526 pub fn mul_scirs2(&self, other: &Self) -> Result<Self>
1528 where
1529 T: std::ops::Mul<Output = T> + scirs2_core::numeric::Float,
1530 {
1531 self.mul(other)
1534 }
1535
1536 pub fn sub_scirs2(&self, other: &Self) -> Result<Self>
1538 where
1539 T: std::ops::Sub<Output = T> + scirs2_core::numeric::Float,
1540 {
1541 self.sub(other)
1544 }
1545
1546 pub fn div_scirs2(&self, other: &Self) -> Result<Self>
1548 where
1549 T: std::ops::Div<Output = T> + scirs2_core::numeric::Float,
1550 {
1551 self.div(other)
1554 }
1555}
1556
1557impl<T: TensorElement + Copy> std::ops::Add for &Tensor<T>
1559where
1560 T: std::ops::Add<Output = T>,
1561{
1562 type Output = Tensor<T>;
1563
1564 fn add(self, rhs: Self) -> Self::Output {
1565 self.add(rhs).expect("tensor addition should succeed")
1566 }
1567}
1568
1569impl<T: TensorElement + Copy> std::ops::Sub for &Tensor<T>
1570where
1571 T: std::ops::Sub<Output = T>,
1572{
1573 type Output = Tensor<T>;
1574
1575 fn sub(self, rhs: Self) -> Self::Output {
1576 self.sub(rhs).expect("tensor subtraction should succeed")
1577 }
1578}
1579
1580impl<T: TensorElement + Copy> std::ops::Mul for &Tensor<T>
1581where
1582 T: std::ops::Mul<Output = T>,
1583{
1584 type Output = Tensor<T>;
1585
1586 fn mul(self, rhs: Self) -> Self::Output {
1587 self.mul(rhs).expect("tensor multiplication should succeed")
1588 }
1589}
1590
1591impl<T: TensorElement + Copy> std::ops::Div for &Tensor<T>
1592where
1593 T: std::ops::Div<Output = T>,
1594{
1595 type Output = Tensor<T>;
1596
1597 fn div(self, rhs: Self) -> Self::Output {
1598 self.div(rhs).expect("tensor division should succeed")
1599 }
1600}
1601
1602impl<T: TensorElement + Copy> std::ops::Neg for &Tensor<T>
1604where
1605 T: std::ops::Neg<Output = T>,
1606{
1607 type Output = Tensor<T>;
1608
1609 fn neg(self) -> Self::Output {
1610 let mut result = self.map(|x| -x).expect("negation map should succeed");
1611 if crate::should_record_grad(self.requires_grad()) {
1614 result.requires_grad = true;
1615 result.operation = Operation::MulScalar {
1616 input: Arc::new(self.clone()),
1617 scalar: -<T as TensorElement>::one(),
1618 };
1619 }
1620 result
1621 }
1622}
1623
1624impl<T: TensorElement + Copy + scirs2_core::numeric::Float> Tensor<T> {
1628 #[cfg(feature = "simd")]
1632 pub fn add_simd(&self, other: &Self) -> Result<Self>
1633 where
1634 T: scirs2_core::simd_ops::SimdUnifiedOps,
1635 {
1636 use scirs2_core::ndarray::Array1;
1637
1638 if self.shape().dims() != other.shape().dims() {
1640 return Err(TorshError::ShapeMismatch {
1641 expected: self.shape().dims().to_vec(),
1642 got: other.shape().dims().to_vec(),
1643 });
1644 }
1645
1646 let data_a = self.to_vec()?;
1648 let data_b = other.to_vec()?;
1649
1650 let arr_a = Array1::from_vec(data_a);
1652 let arr_b = Array1::from_vec(data_b);
1653
1654 let result_arr = T::simd_add(&arr_a.view(), &arr_b.view());
1656
1657 Tensor::from_vec(result_arr.to_vec(), self.shape().dims())
1659 }
1660
1661 #[cfg(feature = "simd")]
1665 pub fn mul_simd(&self, other: &Self) -> Result<Self>
1666 where
1667 T: scirs2_core::simd_ops::SimdUnifiedOps,
1668 {
1669 use scirs2_core::ndarray::Array1;
1670
1671 if self.shape().dims() != other.shape().dims() {
1673 return Err(TorshError::ShapeMismatch {
1674 expected: self.shape().dims().to_vec(),
1675 got: other.shape().dims().to_vec(),
1676 });
1677 }
1678
1679 let data_a = self.to_vec()?;
1681 let data_b = other.to_vec()?;
1682
1683 let arr_a = Array1::from_vec(data_a);
1685 let arr_b = Array1::from_vec(data_b);
1686
1687 let result_arr = T::simd_mul(&arr_a.view(), &arr_b.view());
1689
1690 Tensor::from_vec(result_arr.to_vec(), self.shape().dims())
1692 }
1693
1694 #[cfg(feature = "simd")]
1700 pub fn dot_simd(&self, other: &Self) -> Result<T>
1701 where
1702 T: scirs2_core::simd_ops::SimdUnifiedOps,
1703 {
1704 use scirs2_core::ndarray::Array1;
1705
1706 if self.shape().dims() != other.shape().dims() {
1708 return Err(TorshError::ShapeMismatch {
1709 expected: self.shape().dims().to_vec(),
1710 got: other.shape().dims().to_vec(),
1711 });
1712 }
1713
1714 let data_a = self.to_vec()?;
1716 let data_b = other.to_vec()?;
1717
1718 let arr_a = Array1::from_vec(data_a);
1720 let arr_b = Array1::from_vec(data_b);
1721
1722 Ok(T::simd_dot(&arr_a.view(), &arr_b.view()))
1724 }
1725
1726 pub fn reduce_memory_efficient<F>(&self, func: F) -> Result<T>
1728 where
1729 F: Fn(T, T) -> T + Send + Sync,
1730 {
1731 #[cfg(feature = "profiling")]
1732 {
1733 }
1735
1736 let data = self.to_vec()?;
1739 Ok(data
1740 .into_iter()
1741 .reduce(func)
1742 .unwrap_or_else(|| <T as scirs2_core::numeric::Zero>::zero()))
1743 }
1744}
1745
1746impl<T: TensorElement + Copy + std::ops::Mul<Output = T>> Tensor<T> {
1748 pub fn relu_(&mut self) -> Result<&mut Self>
1756 where
1757 T: std::cmp::PartialOrd + scirs2_core::numeric::Zero,
1758 {
1759 if self.requires_grad {
1760 return Err(TorshError::InvalidArgument(
1761 "In-place operation on tensor that requires grad is not allowed".to_string(),
1762 ));
1763 }
1764
1765 if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() && self.numel() >= 1024 {
1767 self.make_unique()?;
1768 self.storage.with_slice_mut(|out_t: &mut [T]| {
1769 let out_f32: &mut [f32] = unsafe {
1771 std::slice::from_raw_parts_mut(out_t.as_mut_ptr() as *mut f32, out_t.len())
1772 };
1773 crate::simd_ops_f32::relu_assign_f32(out_f32);
1774 Ok(())
1775 })?;
1776 return Ok(self);
1777 }
1778
1779 let zero = <T as scirs2_core::numeric::Zero>::zero();
1782 self.apply_(|x| if x < zero { zero } else { x })?;
1783
1784 Ok(self)
1785 }
1786
1787 pub fn sigmoid_(&mut self) -> Result<&mut Self>
1792 where
1793 T: torsh_core::dtype::FloatElement,
1794 {
1795 if self.requires_grad {
1796 return Err(TorshError::InvalidArgument(
1797 "In-place operation on tensor that requires grad is not allowed".to_string(),
1798 ));
1799 }
1800
1801 let one = <T as scirs2_core::numeric::One>::one();
1803 self.apply_(|x| one / (one + (-x).exp()))?;
1804
1805 Ok(self)
1806 }
1807
1808 pub fn tanh_(&mut self) -> Result<&mut Self>
1813 where
1814 T: torsh_core::dtype::FloatElement,
1815 {
1816 if self.requires_grad {
1817 return Err(TorshError::InvalidArgument(
1818 "In-place operation on tensor that requires grad is not allowed".to_string(),
1819 ));
1820 }
1821
1822 self.apply_(|x| x.tanh())?;
1824
1825 Ok(self)
1826 }
1827
1828 pub fn gelu_(&mut self) -> Result<&mut Self>
1833 where
1834 T: torsh_core::dtype::FloatElement,
1835 {
1836 if self.requires_grad {
1837 return Err(TorshError::InvalidArgument(
1838 "In-place operation on tensor that requires grad is not allowed".to_string(),
1839 ));
1840 }
1841
1842 let constant = |value: f64| -> Result<T> {
1845 T::from(value).ok_or_else(|| {
1846 TorshError::InvalidArgument(format!(
1847 "gelu_: element type cannot represent the constant {value}"
1848 ))
1849 })
1850 };
1851 let pi = constant(std::f64::consts::PI)?;
1852 let two = constant(2.0)?;
1853 let sqrt_2_over_pi = (two / pi).sqrt();
1854 let point_044715 = constant(0.044715)?;
1855 let one = <T as scirs2_core::numeric::One>::one();
1856 let half = constant(0.5)?;
1857
1858 self.apply_(|x| {
1860 let x_cubed = x * x * x;
1861 let tanh_input = sqrt_2_over_pi * (x + point_044715 * x_cubed);
1862 half * x * (one + tanh_input.tanh())
1863 })?;
1864
1865 Ok(self)
1866 }
1867
1868 pub fn leaky_relu_(&mut self, negative_slope: T) -> Result<&mut Self>
1873 where
1874 T: std::cmp::PartialOrd + scirs2_core::numeric::Zero,
1875 {
1876 if self.requires_grad {
1877 return Err(TorshError::InvalidArgument(
1878 "In-place operation on tensor that requires grad is not allowed".to_string(),
1879 ));
1880 }
1881
1882 if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() && self.numel() >= 1024 {
1884 let slope_f32: f32 = unsafe { std::mem::transmute_copy::<T, f32>(&negative_slope) };
1886 self.make_unique()?;
1887 self.storage.with_slice_mut(|out_t: &mut [T]| {
1888 let out_f32: &mut [f32] = unsafe {
1889 std::slice::from_raw_parts_mut(out_t.as_mut_ptr() as *mut f32, out_t.len())
1890 };
1891 crate::simd_ops_f32::leaky_relu_assign_f32(out_f32, slope_f32);
1892 Ok(())
1893 })?;
1894 return Ok(self);
1895 }
1896
1897 let zero = <T as scirs2_core::numeric::Zero>::zero();
1899 self.apply_(|x| if x < zero { negative_slope * x } else { x })?;
1900
1901 Ok(self)
1902 }
1903
1904 pub fn clamp_(&mut self, min: T, max: T) -> Result<&mut Self>
1909 where
1910 T: std::cmp::PartialOrd,
1911 {
1912 if self.requires_grad {
1913 return Err(TorshError::InvalidArgument(
1914 "In-place operation on tensor that requires grad is not allowed".to_string(),
1915 ));
1916 }
1917
1918 if std::any::TypeId::of::<T>() == std::any::TypeId::of::<f32>() && self.numel() >= 1024 {
1920 let min_f32: f32 = unsafe { std::mem::transmute_copy::<T, f32>(&min) };
1922 let max_f32: f32 = unsafe { std::mem::transmute_copy::<T, f32>(&max) };
1923 self.make_unique()?;
1924 self.storage.with_slice_mut(|out_t: &mut [T]| {
1925 let out_f32: &mut [f32] = unsafe {
1926 std::slice::from_raw_parts_mut(out_t.as_mut_ptr() as *mut f32, out_t.len())
1927 };
1928 crate::simd_ops_f32::clamp_assign_f32(out_f32, min_f32, max_f32);
1929 Ok(())
1930 })?;
1931 return Ok(self);
1932 }
1933
1934 self.apply_(|x| {
1936 if x < min {
1937 min
1938 } else if x > max {
1939 max
1940 } else {
1941 x
1942 }
1943 })?;
1944
1945 Ok(self)
1946 }
1947}
1948
1949#[path = "math_ops_trig.rs"]
1950mod math_ops_trig;
1951
1952#[cfg(test)]
1953#[path = "math_ops_tests.rs"]
1954mod tests;