1use std::any::{Any, TypeId};
14use std::collections::HashMap;
15
16use ::ndarray::{Array, Axis, Ix1, Ix2, Ix3, Ix4, IxDyn};
17use rand::{Rng, RngExt, SeedableRng};
18
19use crate::array_protocol::operations::OperationError;
20use crate::array_protocol::{
21 array_function_dispatch, get_implementing_args, ArrayProtocol, NdarrayWrapper,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq)]
26pub enum ActivationFunc {
27 ReLU,
29
30 Sigmoid,
32
33 Tanh,
35
36 Softmax,
38
39 LeakyReLU(f64),
41}
42
43#[allow(dead_code)]
45fn apply_activation(
46 x: &crate::ndarray::ArrayBase<crate::ndarray::ViewRepr<&f64>, IxDyn>,
47 func: ActivationFunc,
48) -> Array<f64, IxDyn> {
49 match func {
50 ActivationFunc::ReLU => x.mapv(|v| v.max(0.0)),
51 ActivationFunc::Sigmoid => x.mapv(|v| 1.0 / (1.0 + (-v).exp())),
52 ActivationFunc::Tanh => x.mapv(|v| v.tanh()),
53 ActivationFunc::LeakyReLU(alpha) => x.mapv(|v| if v > 0.0 { v } else { alpha * v }),
54 ActivationFunc::Softmax => {
55 let mut result = Array::zeros(x.raw_dim());
57
58 let last_dim = x.ndim() - 1;
60 let _last_dim_len = x.shape()[last_dim];
61
62 if x.ndim() == 1 {
63 let max_val = x.fold(f64::NEG_INFINITY, |a, &b| a.max(b));
65 let exp_x = x.mapv(|v| (v - max_val).exp());
66 let sum_exp = exp_x.sum();
67 result.assign(&(exp_x / sum_exp));
68 } else {
69 for (i, mut slice) in result.lanes_mut(Axis(last_dim)).into_iter().enumerate() {
71 let x_slice = x.index_axis(Axis(last_dim), i);
73 let max_val = x_slice.fold(f64::NEG_INFINITY, |a, &b| a.max(b));
74 let exp_x = x_slice.mapv(|v| (v - max_val).exp());
75 let sum_exp = exp_x.sum();
76 slice.assign(&(exp_x / sum_exp));
77 }
78 }
79
80 result
81 }
82 }
83}
84
85array_function_dispatch!(
88 fn activation(
89 x: &dyn ArrayProtocol,
90 func: ActivationFunc,
91 ) -> Result<Box<dyn ArrayProtocol>, OperationError> {
92 let boxed_x = Box::new(x.box_clone());
94 let boxed_args: Vec<Box<dyn Any>> = vec![boxed_x];
95 let implementing_args =
96 get_implementing_args("scirs2::array_protocol::ml_ops::activation", &boxed_args);
97 if implementing_args.is_empty() {
98 if let Some(x_array) = x.as_any().downcast_ref::<NdarrayWrapper<f64, IxDyn>>() {
100 let x_array = x_array.as_array();
101 let result = apply_activation(&x_array.view(), func);
102 return Ok(Box::new(NdarrayWrapper::new(result)));
103 }
104 return Err(OperationError::NotImplemented(
105 "activation not implemented for this array type".to_string(),
106 ));
107 }
108
109 let array_ref = implementing_args[0].1;
111
112 let result = array_ref.array_function(
113 &crate::array_protocol::ArrayFunction::new(
114 "scirs2::array_protocol::ml_ops::activation",
115 ),
116 &[TypeId::of::<Box<dyn ArrayProtocol>>()],
117 &[Box::new(x.box_clone())],
118 &HashMap::new(),
119 )?;
120
121 match result.downcast::<Box<dyn ArrayProtocol>>() {
123 Ok(array) => Ok(*array),
124 Err(_) => Err(OperationError::Other(
125 "Failed to downcast array_function result".to_string(),
126 )),
127 }
128 },
129 "scirs2::array_protocol::ml, ops: activation"
130);
131
132array_function_dispatch!(
133 fn conv2d(
134 input: &dyn ArrayProtocol,
135 filters: &dyn ArrayProtocol,
136 stride: (usize, usize),
137 padding: (usize, usize),
138 ) -> Result<Box<dyn ArrayProtocol>, OperationError> {
139 let boxed_input = Box::new(input.box_clone());
141 let boxed_filters = Box::new(filters.box_clone());
142 let boxed_args: Vec<Box<dyn Any>> = vec![boxed_input, boxed_filters];
143 let implementing_args =
144 get_implementing_args("scirs2::array_protocol::ml_ops::conv2d", &boxed_args);
145 if implementing_args.is_empty() {
146 if let (Some(inputarray), Some(filters_array)) = (
149 input.as_any().downcast_ref::<NdarrayWrapper<f64, Ix4>>(),
150 filters.as_any().downcast_ref::<NdarrayWrapper<f64, Ix4>>(),
151 ) {
152 let input = inputarray.as_array();
153 let filters = filters_array.as_array();
154
155 let batch_size = input.shape()[0];
157 let input_height = input.shape()[1];
158 let input_width = input.shape()[2];
159 let input_channels = input.shape()[3];
160
161 let filter_height = filters.shape()[0];
162 let filter_width = filters.shape()[1];
163 let filter_in_channels = filters.shape()[2];
164 let filter_out_channels = filters.shape()[3];
165
166 if input_channels != filter_in_channels {
168 return Err(OperationError::ShapeMismatch(format!(
169 "Input channels ({input_channels}) doesn't match filter input channels ({filter_in_channels})"
170 )));
171 }
172
173 let out_height = (input_height - filter_height + 2 * padding.0) / stride.0 + 1;
175 let out_width = (input_width - filter_width + 2 * padding.1) / stride.1 + 1;
176
177 let mut output: Array<f64, Ix4> =
179 Array::zeros((batch_size, out_height, out_width, filter_out_channels));
180
181 for b in 0..batch_size {
183 for out_c in 0..filter_out_channels {
184 for out_h in 0..out_height {
185 for out_w in 0..out_width {
186 let mut sum = 0.0;
187
188 for f_h in 0..filter_height {
190 for f_w in 0..filter_width {
191 for in_c in 0..input_channels {
192 let in_h = (out_h * stride.0) as i32 + f_h as i32
194 - padding.0 as i32;
195 let in_w = (out_w * stride.1) as i32 + f_w as i32
196 - padding.1 as i32;
197
198 if in_h >= 0
200 && in_h < input_height as i32
201 && in_w >= 0
202 && in_w < input_width as i32
203 {
204 let input_val =
205 input[[b, in_h as usize, in_w as usize, in_c]];
206 let filter_val = filters[[f_h, f_w, in_c, out_c]];
207 sum += input_val * filter_val;
208 }
209 }
210 }
211 }
212
213 output[[b, out_h, out_w, out_c]] = sum;
214 }
215 }
216 }
217 }
218
219 return Ok(Box::new(NdarrayWrapper::new(output)));
220 }
221 return Err(OperationError::NotImplemented(
222 "conv2d not implemented for these array types".to_string(),
223 ));
224 }
225
226 let mut kwargs = HashMap::new();
228 kwargs.insert("stride".to_string(), Box::new(stride) as Box<dyn Any>);
229 kwargs.insert("padding".to_string(), Box::new(padding) as Box<dyn Any>);
230
231 let array_ref = implementing_args[0].1;
232
233 let result = array_ref.array_function(
234 &crate::array_protocol::ArrayFunction::new("scirs2::array_protocol::ml_ops::conv2d"),
235 &[TypeId::of::<Box<dyn ArrayProtocol>>()],
236 &[Box::new(input.box_clone()), Box::new(filters.box_clone())],
237 &kwargs,
238 )?;
239
240 match result.downcast::<Box<dyn ArrayProtocol>>() {
242 Ok(array) => Ok(*array),
243 Err(_) => Err(OperationError::Other(
244 "Failed to downcast array_function result".to_string(),
245 )),
246 }
247 },
248 "scirs2::array_protocol::ml, ops: conv2d"
249);
250
251array_function_dispatch!(
252 fn max_pool2d(
253 input: &dyn ArrayProtocol,
254 kernel_size: (usize, usize),
255 stride: (usize, usize),
256 padding: (usize, usize),
257 ) -> Result<Box<dyn ArrayProtocol>, OperationError> {
258 let boxed_input = Box::new(input.box_clone());
260 let boxed_args: Vec<Box<dyn Any>> = vec![boxed_input];
261 let implementing_args =
262 get_implementing_args("scirs2::array_protocol::ml_ops::max_pool2d", &boxed_args);
263 if implementing_args.is_empty() {
264 if let Some(inputarray) = input.as_any().downcast_ref::<NdarrayWrapper<f64, Ix4>>() {
266 let input = inputarray.as_array();
267
268 let batch_size = input.shape()[0];
270 let input_height = input.shape()[1];
271 let input_width = input.shape()[2];
272 let channels = input.shape()[3];
273
274 let out_height = (input_height - kernel_size.0 + 2 * padding.0) / stride.0 + 1;
276 let out_width = (input_width - kernel_size.1 + 2 * padding.1) / stride.1 + 1;
277
278 let mut output: Array<f64, Ix4> =
280 Array::zeros((batch_size, out_height, out_width, channels));
281
282 for b in 0..batch_size {
284 for c in 0..channels {
285 for out_h in 0..out_height {
286 for out_w in 0..out_width {
287 let mut max_val = f64::NEG_INFINITY;
288
289 for k_h in 0..kernel_size.0 {
291 for k_w in 0..kernel_size.1 {
292 let in_h = (out_h * stride.0) as i32 + k_h as i32
294 - padding.0 as i32;
295 let in_w = (out_w * stride.1) as i32 + k_w as i32
296 - padding.1 as i32;
297
298 if in_h >= 0
300 && in_h < input_height as i32
301 && in_w >= 0
302 && in_w < input_width as i32
303 {
304 let val = input[[b, in_h as usize, in_w as usize, c]];
305 if val > max_val {
306 max_val = val;
307 }
308 }
309 }
310 }
311
312 output[[b, out_h, out_w, c]] = if max_val == f64::NEG_INFINITY {
314 0.0
315 } else {
316 max_val
317 };
318 }
319 }
320 }
321 }
322
323 return Ok(Box::new(NdarrayWrapper::new(output)));
324 }
325 return Err(OperationError::NotImplemented(
326 "max_pool2d not implemented for this array type".to_string(),
327 ));
328 }
329
330 let mut kwargs = HashMap::new();
332 kwargs.insert(
333 "kernel_size".to_string(),
334 Box::new(kernel_size) as Box<dyn Any>,
335 );
336 kwargs.insert("stride".to_string(), Box::new(stride) as Box<dyn Any>);
337 kwargs.insert("padding".to_string(), Box::new(padding) as Box<dyn Any>);
338
339 let array_ref = implementing_args[0].1;
340
341 let result = array_ref.array_function(
342 &crate::array_protocol::ArrayFunction::new(
343 "scirs2::array_protocol::ml_ops::max_pool2d",
344 ),
345 &[TypeId::of::<Box<dyn ArrayProtocol>>()],
346 &[Box::new(input.box_clone())],
347 &kwargs,
348 )?;
349
350 match result.downcast::<Box<dyn ArrayProtocol>>() {
352 Ok(array) => Ok(*array),
353 Err(_) => Err(OperationError::Other(
354 "Failed to downcast array_function result".to_string(),
355 )),
356 }
357 },
358 "scirs2::array_protocol::ml, ops: max_pool2d"
359);
360
361array_function_dispatch!(
362 fn batch_norm(
363 input: &dyn ArrayProtocol,
364 scale: &dyn ArrayProtocol,
365 offset: &dyn ArrayProtocol,
366 mean: &dyn ArrayProtocol,
367 variance: &dyn ArrayProtocol,
368 epsilon: f64,
369 ) -> Result<Box<dyn ArrayProtocol>, OperationError> {
370 let boxed_args: Vec<Box<dyn Any>> = vec![
372 Box::new(input.box_clone()),
373 Box::new(scale.box_clone()),
374 Box::new(offset.box_clone()),
375 Box::new(mean.box_clone()),
376 Box::new(variance.box_clone()),
377 ];
378 let implementing_args =
379 get_implementing_args("scirs2::array_protocol::ml_ops::batch_norm", &boxed_args);
380 if implementing_args.is_empty() {
381 if let (
383 Some(inputarray),
384 Some(scale_array),
385 Some(offset_array),
386 Some(mean_array),
387 Some(variance_array),
388 ) = (
389 input.as_any().downcast_ref::<NdarrayWrapper<f64, Ix4>>(),
390 scale.as_any().downcast_ref::<NdarrayWrapper<f64, IxDyn>>(),
391 offset.as_any().downcast_ref::<NdarrayWrapper<f64, IxDyn>>(),
392 mean.as_any().downcast_ref::<NdarrayWrapper<f64, IxDyn>>(),
393 variance
394 .as_any()
395 .downcast_ref::<NdarrayWrapper<f64, IxDyn>>(),
396 ) {
397 let input = inputarray.as_array();
398 let scale = scale_array.as_array();
399 let offset = offset_array.as_array();
400 let mean = mean_array.as_array();
401 let variance = variance_array.as_array();
402
403 let _batch_size = input.shape()[0];
405 let _height = input.shape()[1];
406 let _width = input.shape()[2];
407 let channels = input.shape()[3];
408
409 if scale.shape()[0] != channels
411 || offset.shape()[0] != channels
412 || mean.shape()[0] != channels
413 || variance.shape()[0] != channels
414 {
415 return Err(OperationError::ShapeMismatch(
416 "Scale, offset, mean, and variance must match the number of channels"
417 .to_string(),
418 ));
419 }
420
421 let mut output: Array<f64, Ix4> = Array::zeros(input.raw_dim());
423
424 let batch_size = input.shape()[0];
429 let _height = input.shape()[1];
430 let _width = input.shape()[2];
431
432 for b in 0..batch_size {
433 for h in 0.._height {
434 for w in 0.._width {
435 for c in 0..channels {
436 let x = input[[b, h, w, c]];
437 let m = mean[[c]];
438 let v = variance[[c]];
439 let s = scale[[c]];
440 let o = offset[[c]];
441
442 let normalized = (x - m) / (v + epsilon).sqrt();
444
445 let result = s * normalized + o;
447
448 output[[b, h, w, c]] = result;
449 }
450 }
451 }
452 }
453
454 return Ok(Box::new(NdarrayWrapper::new(output)));
455 }
456 return Err(OperationError::NotImplemented(
457 "batch_norm not implemented for these array types".to_string(),
458 ));
459 }
460
461 let mut kwargs = HashMap::new();
463 kwargs.insert("epsilon".to_string(), Box::new(epsilon) as Box<dyn Any>);
464
465 let array_ref = implementing_args[0].1;
466
467 let result = array_ref.array_function(
468 &crate::array_protocol::ArrayFunction::new(
469 "scirs2::array_protocol::ml_ops::batch_norm",
470 ),
471 &[TypeId::of::<Box<dyn ArrayProtocol>>()],
472 &[
473 Box::new(input.box_clone()),
474 Box::new(scale.box_clone()),
475 Box::new(offset.box_clone()),
476 Box::new(mean.box_clone()),
477 Box::new(variance.box_clone()),
478 ],
479 &kwargs,
480 )?;
481
482 match result.downcast::<Box<dyn ArrayProtocol>>() {
484 Ok(array) => Ok(*array),
485 Err(_) => Err(OperationError::Other(
486 "Failed to downcast array_function result".to_string(),
487 )),
488 }
489 },
490 "scirs2::array_protocol::ml, ops: batch_norm"
491);
492
493array_function_dispatch!(
494 fn cross_entropy(
495 logits: &dyn ArrayProtocol,
496 labels: &dyn ArrayProtocol,
497 reduction: &str,
498 ) -> Result<Box<dyn Any>, OperationError> {
499 let boxed_args: Vec<Box<dyn Any>> =
501 vec![Box::new(logits.box_clone()), Box::new(labels.box_clone())];
502 let implementing_args =
503 get_implementing_args("scirs2::array_protocol::ml_ops::cross_entropy", &boxed_args);
504 if implementing_args.is_empty() {
505 if let (Some(logits_array), Some(labels_array)) = (
507 logits.as_any().downcast_ref::<NdarrayWrapper<f64, Ix2>>(),
508 labels.as_any().downcast_ref::<NdarrayWrapper<f64, Ix2>>(),
509 ) {
510 let logits = logits_array.as_array();
511 let labels = labels_array.as_array();
512
513 if logits.shape() != labels.shape() {
515 return Err(OperationError::ShapeMismatch(format!(
516 "Logits shape {logitsshape:?} doesn't match labels shape {labelsshape:?}",
517 logitsshape = logits.shape(),
518 labelsshape = labels.shape()
519 )));
520 }
521
522 let mut softmax = Array::zeros(logits.raw_dim());
524
525 for (i, sample) in logits.outer_iter().enumerate() {
527 let max_val = sample.fold(f64::NEG_INFINITY, |a, &b| a.max(b));
528 let exp_x = sample.mapv(|v| (v - max_val).exp());
529 let sum_exp = exp_x.sum();
530
531 for (j, val) in exp_x.iter().enumerate() {
532 softmax[[i, j]] = val / sum_exp;
533 }
534 }
535
536 let mut sample_losses = Array::zeros(logits.shape()[0]);
539
540 for (i, (s, l)) in softmax.outer_iter().zip(labels.outer_iter()).enumerate() {
541 let mut loss = 0.0;
542 for (s_val, l_val) in s.iter().zip(l.iter()) {
543 loss -= l_val * (s_val + 1e-10).ln();
545 }
546 sample_losses[i] = loss;
547 }
548
549 let loss = match reduction {
551 "none" => sample_losses,
552 "mean" => {
553 let mean = sample_losses.sum() / sample_losses.len() as f64;
554 Array::from_elem(Ix1(1), mean)
556 }
557 "sum" => {
558 let sum = sample_losses.sum();
559 Array::from_elem(Ix1(1), sum)
561 }
562 _ => {
563 return Err(OperationError::ShapeMismatch(format!(
564 "Unknown reduction method: {reduction}"
565 )))
566 }
567 };
568
569 return Ok(Box::new(loss) as Box<dyn Any>);
570 }
571 return Err(OperationError::NotImplemented(
572 "cross_entropy not implemented for these array types".to_string(),
573 ));
574 }
575
576 let mut kwargs = HashMap::new();
578 kwargs.insert(
579 "reduction".to_string(),
580 Box::new(reduction.to_string()) as Box<dyn Any>,
581 );
582
583 let array_ref = implementing_args[0].1;
584
585 let result = array_ref.array_function(
586 &crate::array_protocol::ArrayFunction::new(
587 "scirs2::array_protocol::ml_ops::cross_entropy",
588 ),
589 &[TypeId::of::<Box<dyn Any>>()],
590 &[Box::new(logits.box_clone()), Box::new(labels.box_clone())],
591 &kwargs,
592 )?;
593
594 Ok(result)
595 },
596 "scirs2::array_protocol::ml, ops: cross_entropy"
597);
598
599array_function_dispatch!(
600 fn dropout(
601 input: &dyn ArrayProtocol,
602 rate: f64,
603 training: bool,
604 seed: Option<u64>,
605 ) -> Result<Box<dyn ArrayProtocol>, OperationError> {
606 let boxed_args: Vec<Box<dyn Any>> = vec![Box::new(input.box_clone())];
608 let implementing_args =
609 get_implementing_args("scirs2::array_protocol::ml_ops::dropout", &boxed_args);
610 if implementing_args.is_empty() {
611 if let Some(inputarray) = input.as_any().downcast_ref::<NdarrayWrapper<f64, IxDyn>>() {
613 let input = inputarray.as_array();
614
615 if !training {
616 return Ok(Box::new(NdarrayWrapper::new(input.clone())));
618 }
619
620 let mut rng = match seed {
622 Some(s) => rand::rngs::StdRng::seed_from_u64(s),
623 None => {
624 let mut rng = rand::rng();
625 let random_seed: u64 = rng.random();
627 rand::rngs::StdRng::seed_from_u64(random_seed)
628 }
629 };
630
631 let mask = Array::from_shape_fn(input.raw_dim(), |_| {
632 if rng.random::<f64>() >= rate {
633 1.0
634 } else {
635 0.0
636 }
637 });
638
639 let scale = 1.0 / (1.0 - rate);
641 let result = input.clone() * &mask * scale;
642
643 return Ok(Box::new(NdarrayWrapper::new(result)));
644 }
645 return Err(OperationError::NotImplemented(
646 "dropout not implemented for this array type".to_string(),
647 ));
648 }
649
650 let mut kwargs = HashMap::new();
652 kwargs.insert("rate".to_string(), Box::new(rate) as Box<dyn Any>);
653 kwargs.insert("training".to_string(), Box::new(training) as Box<dyn Any>);
654 if let Some(s) = seed {
655 kwargs.insert("seed".to_string(), Box::new(s) as Box<dyn Any>);
656 }
657
658 let array_ref = implementing_args[0].1;
659
660 let result = array_ref.array_function(
661 &crate::array_protocol::ArrayFunction::new("scirs2::array_protocol::ml_ops::dropout"),
662 &[TypeId::of::<Box<dyn ArrayProtocol>>()],
663 &[Box::new(input.box_clone())],
664 &kwargs,
665 )?;
666
667 match result.downcast::<Box<dyn ArrayProtocol>>() {
669 Ok(array) => Ok(*array),
670 Err(_) => Err(OperationError::Other(
671 "Failed to downcast array_function result".to_string(),
672 )),
673 }
674 },
675 "scirs2::array_protocol::ml, ops: dropout"
676);
677
678array_function_dispatch!(
679 fn self_attention(
680 queries: &dyn ArrayProtocol,
681 keys: &dyn ArrayProtocol,
682 values: &dyn ArrayProtocol,
683 mask: Option<&dyn ArrayProtocol>,
684 scale: Option<f64>,
685 ) -> Result<Box<dyn ArrayProtocol>, OperationError> {
686 let mut boxed_args: Vec<Box<dyn Any>> = vec![
688 Box::new(queries.box_clone()),
689 Box::new(keys.box_clone()),
690 Box::new(values.box_clone()),
691 ];
692 if let Some(m) = mask {
693 boxed_args.push(Box::new(m.box_clone()));
694 }
695
696 let implementing_args = get_implementing_args(
697 "scirs2::array_protocol::ml_ops::self_attention",
698 &boxed_args,
699 );
700 if implementing_args.is_empty() {
701 if let (Some(q_array), Some(k_array), Some(v_array)) = (
703 queries.as_any().downcast_ref::<NdarrayWrapper<f64, Ix4>>(),
704 keys.as_any().downcast_ref::<NdarrayWrapper<f64, Ix4>>(),
705 values.as_any().downcast_ref::<NdarrayWrapper<f64, Ix4>>(),
706 ) {
707 let q = q_array.as_array();
708 let k = k_array.as_array();
709 let v = v_array.as_array();
710
711 let batch_size = q.shape()[0];
714 let q_len = q.shape()[1];
715 let num_heads = q.shape()[2];
716 let d_k = q.shape()[3];
717
718 let k_len = k.shape()[1];
719
720 if k.shape()[0] != batch_size
722 || k.shape()[2] != num_heads
723 || k.shape()[3] != d_k
724 || v.shape()[0] != batch_size
725 || v.shape()[1] != k_len
726 || v.shape()[2] != num_heads
727 {
728 return Err(OperationError::ShapeMismatch(
729 "Incompatible shapes for self-attention".to_string(),
730 ));
731 }
732
733 let _scale_factor = scale.unwrap_or_else(|| {
735 let d_k_f64 = d_k as f64;
737 if d_k_f64 > 0.0 {
738 d_k_f64.sqrt()
739 } else {
740 1.0 }
742 });
743
744 let scale_factor = scale.unwrap_or(1.0 / (d_k as f64).sqrt());
751 let mut output: Array<f64, Ix3> = Array::zeros((batch_size, q_len, d_k));
752
753 for b in 0..batch_size {
754 let q_batch = q.slice(crate::s![b, .., .., ..]);
756 let k_batch = k.slice(crate::s![b, .., .., ..]);
757 let v_batch = v.slice(crate::s![b, .., .., ..]);
758
759 let mut head_outputs = Array::zeros((q_len, num_heads, d_k));
761
762 for h in 0..num_heads {
763 let mut scores = Array::zeros((q_len, k_len));
764 for i in 0..q_len {
765 for j in 0..k_len {
766 let mut dot_product = 0.0;
767 for k in 0..d_k {
768 dot_product += q_batch[[i, h, k]] * k_batch[[j, h, k]];
769 }
770 scores[[i, j]] = dot_product * scale_factor;
771 }
772 }
773
774 if let Some(mask_array) = mask {
776 if let Some(mask_wrapper) = mask_array
777 .as_any()
778 .downcast_ref::<NdarrayWrapper<f64, Ix3>>()
779 {
780 let mask_batch =
781 mask_wrapper.as_array().slice(crate::s![b, .., ..]);
782 for i in 0..q_len {
783 for j in 0..k_len {
784 if mask_batch[[i, j]] == 0.0 {
785 scores[[i, j]] = f64::NEG_INFINITY;
786 }
787 }
788 }
789 }
790 }
791
792 let mut attention = Array::zeros((q_len, k_len));
794 for i in 0..q_len {
795 let mut max_score = f64::NEG_INFINITY;
797 for j in 0..k_len {
798 if scores[[i, j]] > max_score {
799 max_score = scores[[i, j]];
800 }
801 }
802
803 let mut exp_sum = 0.0;
805 for j in 0..k_len {
806 let exp_val = (scores[[i, j]] - max_score).exp();
807 attention[[i, j]] = exp_val;
808 exp_sum += exp_val;
809 }
810
811 for j in 0..k_len {
813 attention[[i, j]] /= exp_sum;
814 }
815 }
816
817 for i in 0..q_len {
819 for k in 0..d_k {
820 let mut weighted_sum = 0.0;
821 for j in 0..k_len {
822 weighted_sum += attention[[i, j]] * v_batch[[j, h, k]];
823 }
824 head_outputs[[i, h, k]] = weighted_sum;
825 }
826 }
827 }
828
829 for i in 0..q_len {
831 for k in 0..d_k {
832 let mut sum = 0.0;
833 for h in 0..num_heads {
834 sum += head_outputs[[i, h, k]];
835 }
836 output[[b, i, k]] = sum / num_heads as f64;
837 }
838 }
839 }
840
841 return Ok(Box::new(NdarrayWrapper::new(output)));
842 }
843 return Err(OperationError::NotImplemented(
844 "self_attention not implemented for these array types".to_string(),
845 ));
846 }
847
848 let mut kwargs = HashMap::new();
850 if let Some(s) = scale {
851 kwargs.insert("scale".to_string(), Box::new(s) as Box<dyn Any>);
852 }
853 if let Some(m) = mask {
854 kwargs.insert("mask".to_string(), Box::new(m.box_clone()) as Box<dyn Any>);
855 }
856
857 let array_ref = implementing_args[0].1;
858
859 let result = array_ref.array_function(
860 &crate::array_protocol::ArrayFunction::new(
861 "scirs2::array_protocol::ml_ops::self_attention",
862 ),
863 &[TypeId::of::<Box<dyn ArrayProtocol>>()],
864 &[
865 Box::new(queries.box_clone()),
866 Box::new(keys.box_clone()),
867 Box::new(values.box_clone()),
868 ],
869 &kwargs,
870 )?;
871
872 match result.downcast::<Box<dyn ArrayProtocol>>() {
874 Ok(array) => Ok(*array),
875 Err(_) => Err(OperationError::Other(
876 "Failed to downcast array_function result".to_string(),
877 )),
878 }
879 },
880 "scirs2::array_protocol::ml, ops: self_attention"
881);