ruda_tensor/ops/modules/
prelu_training.rs1use crate::{Backend, DType, FloatDType, Shape, TensorMetadata, get_device_settings, tensor::FloatTensor};
2
3pub struct PreluGeometry {
5 pub channels: usize,
7 pub spatial: usize,
9 pub parameters: usize,
11 pub elements: usize,
13}
14
15pub fn geometry(input: &Shape, alpha: &Shape) -> PreluGeometry {
17 assert!(input.num_dims() > 0, "PReLU requires at least one input axis");
18 assert_eq!(alpha.num_dims(), 1, "PReLU slope must be a vector");
19 let parameters = alpha[0];
20 let channels = if input.num_dims() >= 2 { input[1] } else { 1 };
21 assert!(parameters == 1 || (input.num_dims() >= 2 && parameters == channels), "PReLU slope must be shared or match actual channels");
22 let spatial = if input.num_dims() >= 2 {
23 input[2..].iter().try_fold(1usize, |size, &extent| size.checked_mul(extent)).expect("PReLU spatial overflow")
24 } else { 1 };
25 let elements = input.iter().try_fold(1usize, |size, &extent| size.checked_mul(extent)).expect("PReLU element count overflow");
26 PreluGeometry { channels, spatial, parameters, elements }
27}
28
29fn broadcast(shape: &Shape, parameters: usize) -> Shape {
30 let mut dims = alloc::vec![1; shape.num_dims()];
31 if parameters != 1 { dims[1] = parameters; }
32 Shape::from(dims)
33}
34
35pub fn prelu_native<B: Backend>(input: FloatTensor<B>, alpha: FloatTensor<B>) -> FloatTensor<B> {
37 let shape = input.shape();
38 let info = geometry(&shape, &alpha.shape());
39 if info.elements == 0 { return input; }
40 let storage: FloatDType = input.dtype().into();
41 let compute = if input.dtype() == DType::F64 || alpha.dtype() == DType::F64 { FloatDType::F64 } else { FloatDType::F32 };
42 let input = B::float_cast(input, compute);
43 let alpha = B::float_reshape(B::float_cast(alpha, compute), broadcast(&shape, info.parameters));
44 B::float_cast(B::prelu(input, alpha), storage)
45}
46
47pub fn prelu_native_backward_select<B: Backend>(input: FloatTensor<B>, alpha: FloatTensor<B>, grad: FloatTensor<B>,
49 mask: [bool; 2]) -> [Option<FloatTensor<B>>; 2] {
50 if mask == [false; 2] { return [None, None]; }
51 let shape = input.shape();
52 let alpha_shape = alpha.shape();
53 let info = geometry(&shape, &alpha_shape);
54 assert_eq!(grad.shape(), shape, "PReLU gradient shape differs");
55 let storage: FloatDType = input.dtype().into();
56 let alpha_storage: FloatDType = alpha.dtype().into();
57 if info.elements == 0 {
58 let device = B::float_device(&alpha);
59 return [mask[0].then_some(input), mask[1].then(|| B::float_zeros(alpha_shape, &device, alpha_storage))];
60 }
61 let compute = if [&input, &alpha, &grad].into_iter().any(|value| value.dtype() == DType::F64) { FloatDType::F64 } else { FloatDType::F32 };
62 let input = B::float_cast(input, compute);
63 let grad = B::float_cast(grad, compute);
64 let bool_dtype = get_device_settings::<B>(&B::float_device(&input)).bool_dtype;
65 let negative = B::float_lower_elem(input.clone(), 0f32.into(), bool_dtype);
66 let input_grad = mask[0].then(|| {
67 let alpha = B::float_reshape(B::float_cast(alpha, compute), broadcast(&shape, info.parameters));
68 let scaled = B::float_mul(grad.clone(), alpha);
69 B::float_cast(B::float_mask_where(grad.clone(), negative.clone(), scaled), storage)
70 });
71 let weight_grad = mask[1].then(|| {
72 let nonnegative = B::bool_not(negative);
73 let product = B::float_mask_fill(B::float_mul(input, grad), nonnegative, 0f32.into());
74 let output = if info.parameters == 1 { B::float_sum(product) } else {
75 let batch = shape[0];
76 let product = B::float_reshape(product, Shape::new([batch, info.channels, info.spatial]));
77 B::float_sum_dim(B::float_sum_dim(product, 0), 2)
78 };
79 B::float_reshape(B::float_cast(output, alpha_storage), alpha_shape)
80 });
81 [input_grad, weight_grad]
82}