1#![allow(unsafe_op_in_unsafe_fn)]
5use crate::thunk::*;
6
7#[allow(unused_variables)]
8pub(crate) fn compile_gather(
9 node: &rlx_ir::Node,
10 graph: &Graph,
11 arena: &crate::arena::Arena,
12 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
13 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
14 rng: rlx_ir::RngOptions,
15) -> Thunk {
16 let Op::Gather { axis } = &node.op else {
17 unreachable!()
18 };
19 {
20 let table_shape = &graph.node(node.inputs[0]).shape;
22 let rank = table_shape.rank();
23 let outer: usize = (0..*axis)
24 .map(|i| table_shape.dim(i).unwrap_static())
25 .product::<usize>()
26 .max(1);
27 let trailing: usize = (*axis + 1..rank)
28 .map(|i| table_shape.dim(i).unwrap_static())
29 .product::<usize>()
30 .max(1);
31 let axis_dim = table_shape.dim(*axis).unwrap_static();
32 let idx_len = get_len(graph, node.inputs[1]);
33 let idx_i64 = u8::from(graph.node(node.inputs[1]).shape.dtype() == rlx_ir::DType::I64);
34 let table_bytes = graph.node(node.inputs[0]).shape.dtype().size_bytes() as u8;
35 Thunk::GatherAxis {
36 table: node_offset(arena, node.inputs[0]),
37 idx: node_offset(arena, node.inputs[1]),
38 dst: node_offset(arena, node.id),
39 outer: outer as u32,
40 axis_dim: axis_dim as u32,
41 num_idx: idx_len as u32,
42 trailing: trailing as u32,
43 idx_i64,
44 table_bytes,
45 }
46 }
47}
48
49#[allow(unused_variables)]
50pub(crate) fn compile_narrow(
51 node: &rlx_ir::Node,
52 graph: &Graph,
53 arena: &crate::arena::Arena,
54 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
55 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
56 rng: rlx_ir::RngOptions,
57) -> Thunk {
58 let Op::Narrow { axis, start, len } = &node.op else {
59 unreachable!()
60 };
61 {
62 let in_shape = &graph.node(node.inputs[0]).shape;
63 let elem_bytes = in_shape.dtype().size_bytes() as u8;
64 let rank = in_shape.rank();
65 let outer: usize = (0..*axis)
66 .map(|i| in_shape.dim(i).unwrap_static())
67 .product::<usize>()
68 .max(1);
69 let inner: usize = (*axis + 1..rank)
70 .map(|i| in_shape.dim(i).unwrap_static())
71 .product::<usize>()
72 .max(1);
73 let in_axis = in_shape.dim(*axis).unwrap_static();
74 let src_byte_offset =
75 node_offset(arena, node.inputs[0]) + start * inner * elem_bytes as usize;
76 Thunk::Narrow {
77 src: src_byte_offset,
78 dst: node_offset(arena, node.id),
79 outer: outer as u32,
80 src_stride: (in_axis * inner) as u32, dst_stride: (*len * inner) as u32, inner: (*len * inner) as u32, elem_bytes,
84 }
85 }
86}
87
88#[allow(unused_variables)]
89pub(crate) fn compile_reverse(
90 node: &rlx_ir::Node,
91 graph: &Graph,
92 arena: &crate::arena::Arena,
93 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
94 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
95 rng: rlx_ir::RngOptions,
96) -> Thunk {
97 let Op::Reverse { axes } = &node.op else {
98 unreachable!()
99 };
100 {
101 let in_shape = &graph.node(node.inputs[0]).shape;
102 let rank = in_shape.rank();
103 let dims: Vec<u32> = (0..rank)
104 .map(|i| in_shape.dim(i).unwrap_static() as u32)
105 .collect();
106 let mut rev_mask = vec![false; rank];
107 for &a in axes {
108 if a < rank {
109 rev_mask[a] = true;
110 }
111 }
112 Thunk::Reverse {
113 src: node_offset(arena, node.inputs[0]),
114 dst: node_offset(arena, node.id),
115 dims,
116 rev_mask,
117 elem_bytes: in_shape.dtype().size_bytes() as u8,
118 }
119 }
120}
121
122#[allow(unused_variables)]
123pub(crate) fn compile_cast(
124 node: &rlx_ir::Node,
125 graph: &Graph,
126 arena: &crate::arena::Arena,
127 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
128 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
129 rng: rlx_ir::RngOptions,
130) -> Thunk {
131 let Op::Cast { to } = &node.op else {
132 unreachable!()
133 };
134 {
135 let in_node = graph.node(node.inputs[0]);
136 let in_dtype = in_node.shape.dtype();
137 let out_dtype = *to;
138 let len = node.shape.num_elements().unwrap();
139 let src = node_offset(arena, node.inputs[0]);
140 let dst = node_offset(arena, node.id);
141 if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::I64 {
142 Thunk::CastF32ToI64 {
143 src,
144 dst,
145 len: len as u32,
146 }
147 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::F64 {
148 Thunk::CastF32ToF64 {
149 src,
150 dst,
151 len: len as u32,
152 }
153 } else if in_dtype == rlx_ir::DType::F64 && out_dtype == rlx_ir::DType::F32 {
154 Thunk::CastF64ToF32 {
158 src,
159 dst,
160 len: len as u32,
161 }
162 } else if in_dtype == rlx_ir::DType::F64 && out_dtype == rlx_ir::DType::I64 {
163 Thunk::CastF64ToI64 {
165 src,
166 dst,
167 len: len as u32,
168 }
169 } else if in_dtype == rlx_ir::DType::F64 && out_dtype == rlx_ir::DType::I32 {
170 Thunk::CastF64ToI32 {
172 src,
173 dst,
174 len: len as u32,
175 }
176 } else if in_dtype == rlx_ir::DType::F64 && out_dtype == rlx_ir::DType::Bool {
177 Thunk::CastF64ToBool {
179 src,
180 dst,
181 len: len as u32,
182 }
183 } else if in_dtype == rlx_ir::DType::I64 && out_dtype == rlx_ir::DType::I32 {
184 Thunk::CastI64ToI32 {
187 src,
188 dst,
189 len: len as u32,
190 }
191 } else if in_dtype == rlx_ir::DType::I64 && out_dtype == rlx_ir::DType::F64 {
192 Thunk::CastI64ToF64 {
194 src,
195 dst,
196 len: len as u32,
197 }
198 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::F64 {
199 Thunk::CastI32ToF64 {
201 src,
202 dst,
203 len: len as u32,
204 }
205 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::F16 {
206 Thunk::CastF32ToF16 {
208 src,
209 dst,
210 len: len as u32,
211 }
212 } else if in_dtype == rlx_ir::DType::F16 && out_dtype == rlx_ir::DType::F32 {
213 Thunk::CastF16ToF32 {
215 src,
216 dst,
217 len: len as u32,
218 }
219 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::BF16 {
220 Thunk::CastF32ToBf16 {
222 src,
223 dst,
224 len: len as u32,
225 }
226 } else if in_dtype == rlx_ir::DType::BF16 && out_dtype == rlx_ir::DType::F32 {
227 Thunk::CastBf16ToF32 {
229 src,
230 dst,
231 len: len as u32,
232 }
233 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::I32 {
234 Thunk::CastF32ToI32 {
235 src,
236 dst,
237 len: len as u32,
238 }
239 } else if in_dtype == rlx_ir::DType::I64 && out_dtype == rlx_ir::DType::F32 {
240 Thunk::CastI64ToF32 {
241 src,
242 dst,
243 len: len as u32,
244 }
245 } else if in_dtype == rlx_ir::DType::Bool && out_dtype == rlx_ir::DType::I32 {
246 Thunk::CastBoolToI32 {
247 src,
248 dst,
249 len: len as u32,
250 }
251 } else if in_dtype == rlx_ir::DType::Bool && out_dtype == rlx_ir::DType::F32 {
252 Thunk::CastBoolToF32 {
255 src,
256 dst,
257 len: len as u32,
258 }
259 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::Bool {
260 Thunk::CastF32ToBool {
263 src,
264 dst,
265 len: len as u32,
266 }
267 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::F32 {
268 Thunk::CastI32ToF32 {
269 src,
270 dst,
271 len: len as u32,
272 }
273 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::I64 {
274 Thunk::CastI32ToI64 {
275 src,
276 dst,
277 len: len as u32,
278 }
279 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::Bool {
280 Thunk::CastI32ToBool {
281 src,
282 dst,
283 len: len as u32,
284 }
285 } else if in_dtype == rlx_ir::DType::I64 && out_dtype == rlx_ir::DType::Bool {
286 Thunk::CastI64ToBool {
287 src,
288 dst,
289 len: len as u32,
290 }
291 } else if in_dtype == rlx_ir::DType::Bool && out_dtype == rlx_ir::DType::I64 {
292 Thunk::CastBoolToI64 {
293 src,
294 dst,
295 len: len as u32,
296 }
297 } else if in_dtype == out_dtype {
298 match out_dtype {
299 rlx_ir::DType::F64 => Thunk::CopyF64 {
300 src,
301 dst,
302 len: len as u32,
303 },
304 rlx_ir::DType::I64 => Thunk::CopyI64 {
305 src,
306 dst,
307 len: len as u32,
308 },
309 rlx_ir::DType::C128 => Thunk::CastGeneric {
314 src,
315 dst,
316 len: len as u32,
317 src_dtype: in_dtype,
318 dst_dtype: out_dtype,
319 },
320 _ => Thunk::Copy {
321 src,
322 dst,
323 len: len as u32,
324 },
325 }
326 } else {
327 Thunk::CastGeneric {
333 src,
334 dst,
335 len: len as u32,
336 src_dtype: in_dtype,
337 dst_dtype: out_dtype,
338 }
339 }
340 }
341}
342
343#[derive(Clone, Copy)]
349enum CastScalar {
350 F(f64),
351 I(i64),
352 C(f64, f64),
353}
354
355impl CastScalar {
356 #[inline]
359 fn real(self) -> f64 {
360 match self {
361 CastScalar::F(f) => f,
362 CastScalar::I(i) => i as f64,
363 CastScalar::C(re, _) => re,
364 }
365 }
366
367 #[inline]
370 fn truthy(self) -> bool {
371 match self {
372 CastScalar::F(f) => f != 0.0,
373 CastScalar::I(i) => i != 0,
374 CastScalar::C(re, im) => re != 0.0 || im != 0.0,
375 }
376 }
377
378 #[inline]
381 fn complex(self) -> (f32, f32) {
382 match self {
383 CastScalar::F(f) => (f as f32, 0.0),
384 CastScalar::I(i) => (i as f32, 0.0),
385 CastScalar::C(re, im) => (re as f32, im as f32),
386 }
387 }
388
389 #[inline]
393 fn complex_f64(self) -> (f64, f64) {
394 match self {
395 CastScalar::F(f) => (f, 0.0),
396 CastScalar::I(i) => (i as f64, 0.0),
397 CastScalar::C(re, im) => (re, im),
398 }
399 }
400}
401
402#[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn exec_cast_generic(
421 src: usize,
422 dst: usize,
423 len: usize,
424 src_dtype: rlx_ir::DType,
425 dst_dtype: rlx_ir::DType,
426 base: *mut u8,
427) {
428 use rlx_ir::DType as DT;
429 if len == 0 {
430 return;
431 }
432 let mut vals: Vec<CastScalar> = Vec::with_capacity(len);
436 unsafe {
437 match src_dtype {
438 DT::F32 => {
439 let s = sl(src, base, len);
440 for i in 0..len {
441 vals.push(CastScalar::F(s[i] as f64));
442 }
443 }
444 DT::F64 => {
445 let s = sl_f64(src, base, len);
446 for i in 0..len {
447 vals.push(CastScalar::F(s[i]));
448 }
449 }
450 DT::F16 => {
451 let s = sl_f16(src, base, len);
452 for i in 0..len {
453 vals.push(CastScalar::F(s[i].to_f32() as f64));
454 }
455 }
456 DT::BF16 => {
457 let s = sl_bf16(src, base, len);
458 for i in 0..len {
459 vals.push(CastScalar::F(s[i].to_f32() as f64));
460 }
461 }
462 DT::I8 => {
463 let s = sl_i8(src, base, len);
464 for i in 0..len {
465 vals.push(CastScalar::I(s[i] as i64));
466 }
467 }
468 DT::I16 => {
469 let s = sl_i16(src, base, len);
470 for i in 0..len {
471 vals.push(CastScalar::I(s[i] as i64));
472 }
473 }
474 DT::I32 => {
475 let s = sl_i32(src, base, len);
476 for i in 0..len {
477 vals.push(CastScalar::I(s[i] as i64));
478 }
479 }
480 DT::I64 => {
481 let s = sl_i64(src, base, len);
482 for i in 0..len {
483 vals.push(CastScalar::I(s[i]));
484 }
485 }
486 DT::U8 => {
487 let s = sl_u8(src, base, len);
488 for i in 0..len {
489 vals.push(CastScalar::I(s[i] as i64));
490 }
491 }
492 DT::U32 => {
493 let s = sl_u32(src, base, len);
494 for i in 0..len {
495 vals.push(CastScalar::I(s[i] as i64));
496 }
497 }
498 DT::Bool => {
499 let s = sl_u8(src, base, len);
500 for i in 0..len {
501 vals.push(CastScalar::I(i64::from(s[i] != 0)));
502 }
503 }
504 DT::C64 => {
505 let s = sl(src, base, 2 * len);
507 for i in 0..len {
508 vals.push(CastScalar::C(s[2 * i] as f64, s[2 * i + 1] as f64));
509 }
510 }
511 DT::C128 => {
512 let s = sl_f64(src, base, 2 * len);
514 for i in 0..len {
515 vals.push(CastScalar::C(s[2 * i], s[2 * i + 1]));
516 }
517 }
518 }
519
520 macro_rules! write_int {
523 ($accessor:ident, $t:ty) => {{
524 let o = $accessor(dst, base, len);
525 for i in 0..len {
526 o[i] = match vals[i] {
527 CastScalar::I(n) => n as $t,
528 CastScalar::F(f) => f as $t,
529 CastScalar::C(re, _) => re as $t,
530 };
531 }
532 }};
533 }
534 match dst_dtype {
535 DT::F32 => {
536 let o = sl_mut(dst, base, len);
537 for i in 0..len {
538 o[i] = vals[i].real() as f32;
539 }
540 }
541 DT::F64 => {
542 let o = sl_mut_f64(dst, base, len);
543 for i in 0..len {
544 o[i] = vals[i].real();
545 }
546 }
547 DT::F16 => {
548 let o = sl_mut_f16(dst, base, len);
549 for i in 0..len {
550 o[i] = half::f16::from_f32(vals[i].real() as f32);
551 }
552 }
553 DT::BF16 => {
554 let o = sl_mut_bf16(dst, base, len);
555 for i in 0..len {
556 o[i] = half::bf16::from_f32(vals[i].real() as f32);
557 }
558 }
559 DT::I8 => write_int!(sl_mut_i8, i8),
560 DT::I16 => write_int!(sl_mut_i16, i16),
561 DT::I32 => write_int!(sl_mut_i32, i32),
562 DT::I64 => write_int!(sl_mut_i64, i64),
563 DT::U8 => write_int!(sl_mut_u8, u8),
564 DT::U32 => write_int!(sl_mut_u32, u32),
565 DT::Bool => {
566 let o = sl_mut_u8(dst, base, len);
567 for i in 0..len {
568 o[i] = u8::from(vals[i].truthy());
569 }
570 }
571 DT::C64 => {
572 let o = sl_mut(dst, base, 2 * len);
574 for i in 0..len {
575 let (re, im) = vals[i].complex();
576 o[2 * i] = re;
577 o[2 * i + 1] = im;
578 }
579 }
580 DT::C128 => {
581 let o = sl_mut_f64(dst, base, 2 * len);
583 for i in 0..len {
584 let (re, im) = vals[i].complex_f64();
585 o[2 * i] = re;
586 o[2 * i + 1] = im;
587 }
588 }
589 }
590 }
591}
592
593#[allow(unused_variables)]
594pub(crate) fn compile_expand(
595 node: &rlx_ir::Node,
596 graph: &Graph,
597 arena: &crate::arena::Arena,
598 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
599 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
600 rng: rlx_ir::RngOptions,
601) -> Thunk {
602 let Op::Expand { .. } = &node.op else {
603 unreachable!()
604 };
605 {
606 let in_shape = &graph.node(node.inputs[0]).shape;
611 let out_shape = &node.shape;
612 let in_rank = in_shape.rank();
613 let out_rank = out_shape.rank();
614 let pad = out_rank.saturating_sub(in_rank);
616 let in_dims: Vec<usize> = (0..out_rank)
617 .map(|i| {
618 if i < pad {
619 1
620 } else {
621 in_shape.dim(i - pad).unwrap_static()
622 }
623 })
624 .collect();
625 let mut in_strides_full = vec![1usize; out_rank];
627 for d in (0..out_rank.saturating_sub(1)).rev() {
628 in_strides_full[d] = in_strides_full[d + 1] * in_dims[d + 1];
629 }
630 let out_dims: Vec<u32> = (0..out_rank)
631 .map(|i| out_shape.dim(i).unwrap_static() as u32)
632 .collect();
633 let in_strides: Vec<u32> = (0..out_rank)
635 .map(|i| {
636 if in_dims[i] == 1 && (out_dims[i] as usize) > 1 {
637 0
638 } else {
639 in_strides_full[i] as u32
640 }
641 })
642 .collect();
643 let in_total = in_dims.iter().product::<usize>() as u32;
644 let src = node_offset(arena, node.inputs[0]);
645 let dst = node_offset(arena, node.id);
646 let elem_bytes = node.shape.dtype().size_bytes() as u8;
647 match node.shape.dtype() {
648 rlx_ir::DType::F64 => Thunk::TransposeF64 {
649 src,
650 dst,
651 in_total,
652 out_dims,
653 in_strides,
654 },
655 _ => Thunk::Transpose {
656 src,
657 dst,
658 in_total,
659 out_dims,
660 in_strides,
661 elem_bytes,
662 },
663 }
664 }
665}
666
667#[allow(unused_variables)]
668pub(crate) fn compile_transpose(
669 node: &rlx_ir::Node,
670 graph: &Graph,
671 arena: &crate::arena::Arena,
672 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
673 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
674 rng: rlx_ir::RngOptions,
675) -> Thunk {
676 let Op::Transpose { perm } = &node.op else {
677 unreachable!()
678 };
679 {
680 let in_shape = &graph.node(node.inputs[0]).shape;
683 let in_rank = in_shape.rank();
684 if perm.iter().any(|&p| p >= in_rank) {
685 Thunk::Nop
686 } else {
687 let in_dims: Vec<usize> = (0..in_rank)
688 .map(|i| in_shape.dim(i).unwrap_static())
689 .collect();
690 let mut in_strides_full = vec![1usize; in_rank];
692 for d in (0..in_rank.saturating_sub(1)).rev() {
693 in_strides_full[d] = in_strides_full[d + 1] * in_dims[d + 1];
694 }
695 let out_dims: Vec<u32> = perm.iter().map(|&p| in_dims[p] as u32).collect();
696 let in_strides: Vec<u32> = perm.iter().map(|&p| in_strides_full[p] as u32).collect();
697 let in_total = in_dims.iter().product::<usize>() as u32;
698 let src = node_offset(arena, node.inputs[0]);
699 let dst = node_offset(arena, node.id);
700 let elem_bytes = node.shape.dtype().size_bytes() as u8;
701 match node.shape.dtype() {
702 rlx_ir::DType::F64 => Thunk::TransposeF64 {
703 src,
704 dst,
705 in_total,
706 out_dims,
707 in_strides,
708 },
709 _ => Thunk::Transpose {
710 src,
711 dst,
712 in_total,
713 out_dims,
714 in_strides,
715 elem_bytes,
716 },
717 }
718 }
719 }
720}
721
722#[allow(unused_variables)]
723pub(crate) fn compile_scatter_add(
724 node: &rlx_ir::Node,
725 graph: &Graph,
726 arena: &crate::arena::Arena,
727 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
728 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
729 rng: rlx_ir::RngOptions,
730) -> Thunk {
731 let Op::ScatterAdd = &node.op else {
732 unreachable!()
733 };
734 {
735 let upd_shape = &graph.node(node.inputs[0]).shape;
738 let out_shape = &node.shape;
739 let num_updates = upd_shape.dim(0).unwrap_static();
740 let out_dim = out_shape.dim(0).unwrap_static();
741 let trailing: usize = (1..out_shape.rank())
742 .map(|i| out_shape.dim(i).unwrap_static())
743 .product::<usize>()
744 .max(1);
745 Thunk::ScatterAdd {
746 updates: node_offset(arena, node.inputs[0]),
747 indices: node_offset(arena, node.inputs[1]),
748 dst: node_offset(arena, node.id),
749 num_updates: num_updates as u32,
750 out_dim: out_dim as u32,
751 trailing: trailing as u32,
752 }
753 }
754}
755
756#[allow(unused_variables)]
757pub(crate) fn compile_scatter_nd(
758 node: &rlx_ir::Node,
759 graph: &Graph,
760 arena: &crate::arena::Arena,
761 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
762 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
763 rng: rlx_ir::RngOptions,
764) -> Thunk {
765 let Op::ScatterNd { reduction } = &node.op else {
766 unreachable!()
767 };
768 let data_shape = &graph.node(node.inputs[0]).shape;
769 let indices_shape = &graph.node(node.inputs[1]).shape;
770 let updates_shape = &graph.node(node.inputs[2]).shape;
771 let data_len = data_shape.num_elements().unwrap_or(0);
772 let updates_len = updates_shape.num_elements().unwrap_or(0);
773 let indices_len = indices_shape.num_elements().unwrap_or(0);
774 let indices_i64 = u8::from(indices_shape.dtype() == rlx_ir::DType::I64);
775 Thunk::ScatterNd {
776 data: node_offset(arena, node.inputs[0]),
777 indices: node_offset(arena, node.inputs[1]),
778 updates: node_offset(arena, node.inputs[2]),
779 dst: node_offset(arena, node.id),
780 data_shape: (0..data_shape.rank())
781 .map(|i| data_shape.dim(i).unwrap_static() as u32)
782 .collect(),
783 indices_shape: (0..indices_shape.rank())
784 .map(|i| indices_shape.dim(i).unwrap_static() as u32)
785 .collect(),
786 data_len: data_len as u32,
787 updates_len: updates_len as u32,
788 indices_len: indices_len as u32,
789 indices_i64,
790 reduction: *reduction,
791 }
792}
793
794fn shape_u32s(shape: &rlx_ir::Shape) -> Vec<u32> {
795 (0..shape.rank())
796 .map(|i| shape.dim(i).unwrap_static() as u32)
797 .collect()
798}
799
800#[allow(unused_variables)]
801pub(crate) fn compile_scatter_elements(
802 node: &rlx_ir::Node,
803 graph: &Graph,
804 arena: &crate::arena::Arena,
805 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
806 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
807 rng: rlx_ir::RngOptions,
808) -> Thunk {
809 let Op::ScatterElements { axis, reduction } = &node.op else {
810 unreachable!()
811 };
812 let data_shape = &graph.node(node.inputs[0]).shape;
813 let indices_shape = &graph.node(node.inputs[1]).shape;
814 let updates_shape = &graph.node(node.inputs[2]).shape;
815 Thunk::ScatterElements {
816 data: node_offset(arena, node.inputs[0]),
817 indices: node_offset(arena, node.inputs[1]),
818 updates: node_offset(arena, node.inputs[2]),
819 dst: node_offset(arena, node.id),
820 data_shape: shape_u32s(data_shape),
821 data_len: data_shape.num_elements().unwrap_or(0) as u32,
822 updates_len: updates_shape.num_elements().unwrap_or(0) as u32,
823 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
824 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
825 axis: *axis,
826 reduction: *reduction,
827 }
828}
829
830#[allow(unused_variables)]
831pub(crate) fn compile_gather_nd(
832 node: &rlx_ir::Node,
833 graph: &Graph,
834 arena: &crate::arena::Arena,
835 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
836 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
837 rng: rlx_ir::RngOptions,
838) -> Thunk {
839 let Op::GatherNd { batch_dims } = &node.op else {
840 unreachable!()
841 };
842 let data_shape = &graph.node(node.inputs[0]).shape;
843 let indices_shape = &graph.node(node.inputs[1]).shape;
844 Thunk::GatherNd {
845 data: node_offset(arena, node.inputs[0]),
846 indices: node_offset(arena, node.inputs[1]),
847 dst: node_offset(arena, node.id),
848 data_shape: shape_u32s(data_shape),
849 indices_shape: shape_u32s(indices_shape),
850 data_len: data_shape.num_elements().unwrap_or(0) as u32,
851 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
852 out_len: node.shape.num_elements().unwrap_or(0) as u32,
853 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
854 batch_dims: *batch_dims,
855 }
856}
857
858#[allow(unused_variables)]
859pub(crate) fn compile_gather_elements(
860 node: &rlx_ir::Node,
861 graph: &Graph,
862 arena: &crate::arena::Arena,
863 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
864 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
865 rng: rlx_ir::RngOptions,
866) -> Thunk {
867 let Op::GatherElements { axis } = &node.op else {
868 unreachable!()
869 };
870 let data_shape = &graph.node(node.inputs[0]).shape;
871 let indices_shape = &graph.node(node.inputs[1]).shape;
872 Thunk::GatherElements {
873 data: node_offset(arena, node.inputs[0]),
874 indices: node_offset(arena, node.inputs[1]),
875 dst: node_offset(arena, node.id),
876 data_shape: shape_u32s(data_shape),
877 indices_shape: shape_u32s(indices_shape),
878 data_len: data_shape.num_elements().unwrap_or(0) as u32,
879 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
880 out_len: node.shape.num_elements().unwrap_or(0) as u32,
881 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
882 data_elem_bytes: data_shape.dtype().size_bytes() as u8,
883 axis: *axis,
884 }
885}
886
887#[allow(unused_variables)]
888pub(crate) fn compile_compare(
889 node: &rlx_ir::Node,
890 graph: &Graph,
891 arena: &crate::arena::Arena,
892 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
893 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
894 rng: rlx_ir::RngOptions,
895) -> Thunk {
896 let Op::Compare(cmp) = &node.op else {
897 unreachable!()
898 };
899 {
900 let len = node.shape.num_elements().unwrap();
901 let lhs_n = graph.node(node.inputs[0]).shape.num_elements().unwrap();
902 let rhs_n = graph.node(node.inputs[1]).shape.num_elements().unwrap();
903 let lhs_scalar = lhs_n == 1 && len > 1;
904 let rhs_scalar = rhs_n == 1 && len > 1;
905 let in_dtype = graph.node(node.inputs[0]).shape.dtype();
906 let inputs_i64 = u8::from(in_dtype == rlx_ir::DType::I64);
907 let lhs_partial = lhs_n != len && lhs_n != 1;
915 let rhs_partial = rhs_n != len && rhs_n != 1;
916 let out_dims_s = get_static_dims(graph, node.id);
917 let lhs_dims_s = get_static_dims(graph, node.inputs[0]);
918 let rhs_dims_s = get_static_dims(graph, node.inputs[1]);
919 let (out_dims_bcast, bcast_lhs_strides, bcast_rhs_strides) = if (lhs_partial || rhs_partial)
920 && !out_dims_s.is_empty()
921 && !lhs_dims_s.is_empty()
922 && !rhs_dims_s.is_empty()
923 {
924 (
925 out_dims_s.iter().map(|&d| d as u32).collect::<Vec<u32>>(),
926 broadcast_strides(&lhs_dims_s, &out_dims_s),
927 broadcast_strides(&rhs_dims_s, &out_dims_s),
928 )
929 } else {
930 (Vec::new(), Vec::new(), Vec::new())
931 };
932 Thunk::Compare {
933 lhs: node_offset(arena, node.inputs[0]),
934 rhs: node_offset(arena, node.inputs[1]),
935 dst: node_offset(arena, node.id),
936 len: len as u32,
937 op: *cmp,
938 inputs_i64,
939 inputs_elem_bytes: in_dtype.size_bytes() as u8,
940 dst_elem_bytes: node.shape.dtype().size_bytes() as u8,
941 lhs_scalar,
942 rhs_scalar,
943 lhs_len: lhs_n as u32,
944 rhs_len: rhs_n as u32,
945 out_dims_bcast,
946 bcast_lhs_strides,
947 bcast_rhs_strides,
948 }
949 }
950}
951
952#[allow(unused_variables)]
953pub(crate) fn compile_where(
954 node: &rlx_ir::Node,
955 graph: &Graph,
956 arena: &crate::arena::Arena,
957 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
958 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
959 rng: rlx_ir::RngOptions,
960) -> Thunk {
961 let Op::Where = &node.op else { unreachable!() };
962 {
963 let len = node.shape.num_elements().unwrap();
964 let cn = graph.node(node.inputs[0]).shape.num_elements().unwrap();
965 let tn = graph.node(node.inputs[1]).shape.num_elements().unwrap();
966 let fn_ = graph.node(node.inputs[2]).shape.num_elements().unwrap();
967 let cond_scalar = cn == 1 && len > 1;
968 let true_scalar = tn == 1 && len > 1;
969 let false_scalar = fn_ == 1 && len > 1;
970 let elem_bytes = node.shape.dtype().size_bytes() as u8;
971 let cond_elem_bytes = graph.node(node.inputs[0]).shape.dtype().size_bytes() as u8;
972 Thunk::Where {
973 cond: node_offset(arena, node.inputs[0]),
974 on_true: node_offset(arena, node.inputs[1]),
975 on_false: node_offset(arena, node.inputs[2]),
976 dst: node_offset(arena, node.id),
977 len: len as u32,
978 elem_bytes,
979 cond_elem_bytes,
980 cond_scalar,
981 true_scalar,
982 false_scalar,
983 }
984 }
985}
986
987#[allow(unused_variables)]
988pub(crate) fn compile_fma(
989 node: &rlx_ir::Node,
990 graph: &Graph,
991 arena: &crate::arena::Arena,
992 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
993 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
994 rng: rlx_ir::RngOptions,
995) -> Thunk {
996 let Op::Fma = &node.op else { unreachable!() };
997 {
998 let len = node.shape.num_elements().unwrap();
999 Thunk::Fma {
1000 a: node_offset(arena, node.inputs[0]),
1001 b: node_offset(arena, node.inputs[1]),
1002 c: node_offset(arena, node.inputs[2]),
1003 dst: node_offset(arena, node.id),
1004 len: len as u32,
1005 elem_bytes: node.shape.dtype().size_bytes() as u8,
1006 }
1007 }
1008}
1009
1010#[allow(unused_variables)]
1011pub(crate) fn compile_relu_backward(
1012 node: &rlx_ir::Node,
1013 graph: &Graph,
1014 arena: &crate::arena::Arena,
1015 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1016 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1017 rng: rlx_ir::RngOptions,
1018) -> Thunk {
1019 let Op::ReluBackward = &node.op else {
1020 unreachable!()
1021 };
1022 {
1023 let len: usize = (0..node.shape.rank())
1024 .map(|i| node.shape.dim(i).unwrap_static())
1025 .product();
1026 let x = node_offset(arena, node.inputs[0]);
1027 let dy = node_offset(arena, node.inputs[1]);
1028 let dx = node_offset(arena, node.id);
1029 match node.shape.dtype() {
1030 rlx_ir::DType::F64 => Thunk::ReluBackwardF64 {
1031 x,
1032 dy,
1033 dx,
1034 len: len as u32,
1035 },
1036 _ => Thunk::ReluBackward {
1037 x,
1038 dy,
1039 dx,
1040 len: len as u32,
1041 },
1042 }
1043 }
1044}
1045
1046#[allow(unused_variables)]
1047pub(crate) fn compile_activation_backward(
1048 node: &rlx_ir::Node,
1049 graph: &Graph,
1050 arena: &crate::arena::Arena,
1051 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1052 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1053 rng: rlx_ir::RngOptions,
1054) -> Thunk {
1055 let Op::ActivationBackward { kind } = &node.op else {
1056 unreachable!()
1057 };
1058 {
1059 let len: usize = (0..node.shape.rank())
1060 .map(|i| node.shape.dim(i).unwrap_static())
1061 .product();
1062 let x = node_offset(arena, node.inputs[0]);
1063 let dy = node_offset(arena, node.inputs[1]);
1064 let dx = node_offset(arena, node.id);
1065 match node.shape.dtype() {
1066 rlx_ir::DType::F64 => Thunk::ActivationBackwardF64 {
1067 x,
1068 dy,
1069 dx,
1070 len: len as u32,
1071 kind: *kind,
1072 },
1073 _ => Thunk::ActivationBackward {
1074 x,
1075 dy,
1076 dx,
1077 len: len as u32,
1078 kind: *kind,
1079 },
1080 }
1081 }
1082}
1083
1084#[allow(unused_variables)]
1085pub(crate) fn compile_gather_backward(
1086 node: &rlx_ir::Node,
1087 graph: &Graph,
1088 arena: &crate::arena::Arena,
1089 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1090 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1091 rng: rlx_ir::RngOptions,
1092) -> Thunk {
1093 let Op::GatherBackward { .. } = &node.op else {
1094 unreachable!()
1095 };
1096 {
1097 let dy_shape = &graph.node(node.inputs[0]).shape;
1098 let idx_shape = &graph.node(node.inputs[1]).shape;
1099 let out_shape = &node.shape;
1100 let rank = out_shape.rank();
1101 let axis = match &node.op {
1102 Op::GatherBackward { axis } => *axis,
1103 _ => 0,
1104 };
1105 let axis_u = if axis < 0 {
1106 (rank as i32 + axis) as usize
1107 } else {
1108 axis as usize
1109 };
1110 let outer: usize = (0..axis_u)
1111 .map(|i| dy_shape.dim(i).unwrap_static())
1112 .product::<usize>()
1113 .max(1);
1114 let num_idx = idx_shape.dim(axis_u).unwrap_static();
1115 let trailing: usize = (axis_u + 1..dy_shape.rank())
1116 .map(|i| dy_shape.dim(i).unwrap_static())
1117 .product::<usize>()
1118 .max(1);
1119 let axis_dim = out_shape.dim(axis_u).unwrap_static();
1120 Thunk::GatherBackward {
1121 dy: node_offset(arena, node.inputs[0]),
1122 indices: node_offset(arena, node.inputs[1]),
1123 dst: node_offset(arena, node.id),
1124 outer: outer as u32,
1125 axis_dim: axis_dim as u32,
1126 num_idx: num_idx as u32,
1127 trailing: trailing as u32,
1128 }
1129 }
1130}
1131
1132#[allow(unused_variables)]
1133pub(crate) fn compile_concat(
1134 node: &rlx_ir::Node,
1135 graph: &Graph,
1136 arena: &crate::arena::Arena,
1137 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1138 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1139 rng: rlx_ir::RngOptions,
1140) -> Thunk {
1141 let Op::Concat { axis } = &node.op else {
1142 unreachable!()
1143 };
1144 {
1145 let out_shape = &node.shape;
1149 let rank = out_shape.rank();
1150 let outer: usize = (0..*axis)
1151 .map(|i| out_shape.dim(i).unwrap_static())
1152 .product::<usize>()
1153 .max(1);
1154 let inner: usize = (*axis + 1..rank)
1155 .map(|i| out_shape.dim(i).unwrap_static())
1156 .product::<usize>()
1157 .max(1);
1158 let total_axis = out_shape.dim(*axis).unwrap_static();
1159 let inputs: Vec<(usize, u32, u32)> = node
1160 .inputs
1161 .iter()
1162 .map(|&in_id| {
1163 let in_shape = &graph.node(in_id).shape;
1164 let in_axis = concat_axis_extent(in_shape, *axis, rank);
1165 let in_numel = in_shape.num_elements().unwrap_or(0) as u32;
1166 (node_offset(arena, in_id), in_axis as u32, in_numel)
1167 })
1168 .collect();
1169 let dst = node_offset(arena, node.id);
1170 match out_shape.dtype().size_bytes() {
1178 8 => Thunk::ConcatF64 {
1179 dst,
1180 outer: outer as u32,
1181 inner: inner as u32,
1182 total_axis: total_axis as u32,
1183 inputs,
1184 },
1185 _ => Thunk::Concat {
1186 dst,
1187 outer: outer as u32,
1188 inner: inner as u32,
1189 total_axis: total_axis as u32,
1190 inputs,
1191 },
1192 }
1193 }
1194}
1195
1196#[allow(unused_variables)]
1197pub(crate) fn compile_elementwise_region(
1198 node: &rlx_ir::Node,
1199 graph: &Graph,
1200 arena: &crate::arena::Arena,
1201 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1202 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1203 rng: rlx_ir::RngOptions,
1204) -> Thunk {
1205 let Op::ElementwiseRegion {
1206 chain,
1207 scalar_input_mask,
1208 input_modulus,
1209 prologue,
1210 ..
1211 } = &node.op
1212 else {
1213 unreachable!()
1214 };
1215 {
1216 if *prologue != rlx_ir::op::RegionPrologue::None {
1220 Thunk::Nop
1221 } else {
1222 let input_offs: Vec<usize> = node
1223 .inputs
1224 .iter()
1225 .map(|&id| node_offset(arena, id))
1226 .collect();
1227 Thunk::ElementwiseRegion {
1228 dst: node_offset(arena, node.id),
1229 len: node.shape.num_elements().unwrap_or(0) as u32,
1230 input_offs,
1231 chain: chain.clone(),
1232 scalar_input_mask: *scalar_input_mask,
1233 input_modulus: *input_modulus,
1234 }
1235 }
1236 }
1237}
1238
1239pub(crate) fn get_len(graph: &Graph, id: NodeId) -> usize {
1240 graph.node(id).shape.num_elements().unwrap_or(0)
1241}
1242
1243pub(crate) fn get_static_dims(graph: &Graph, id: NodeId) -> Vec<usize> {
1245 let dims = graph.node(id).shape.dims();
1246 let mut out = Vec::with_capacity(dims.len());
1247 for d in dims {
1248 if let Some(s) = match d {
1249 rlx_ir::Dim::Static(s) => Some(*s),
1250 _ => None,
1251 } {
1252 out.push(s);
1253 } else {
1254 return Vec::new();
1255 }
1256 }
1257 out
1258}
1259
1260pub(crate) fn concat_axis_extent(input: &rlx_ir::Shape, axis: usize, out_rank: usize) -> usize {
1263 let in_rank = input.rank();
1264 if axis >= out_rank {
1265 return 1;
1266 }
1267 if axis < in_rank {
1268 input.dim(axis).unwrap_static()
1269 } else {
1270 1
1271 }
1272}
1273
1274pub(crate) fn broadcast_src_index(src_idx: usize, in_len: usize) -> usize {
1275 if in_len == 0 { 0 } else { src_idx % in_len }
1276}
1277
1278macro_rules! concat_copy_rows {
1282 ($name:ident, $t:ty) => {
1283 pub(crate) fn $name(
1284 out: &mut [$t],
1285 inp: &[$t],
1286 outer: usize,
1287 copy_per_row: usize,
1288 row_stride: usize,
1289 dst_col_off: usize,
1290 in_numel: usize,
1291 ) {
1292 let need = outer.saturating_mul(copy_per_row.max(1));
1293 let broadcast_outer = in_numel < need;
1294 let out_ptr = out.as_mut_ptr() as usize;
1295 let inp_ptr = inp.as_ptr() as usize;
1296 let inp_len = inp.len();
1297 let run = |o0: usize, o1: usize| {
1298 let out = unsafe { std::slice::from_raw_parts_mut(out_ptr as *mut $t, out.len()) };
1299 let inp = unsafe { std::slice::from_raw_parts(inp_ptr as *const $t, inp_len) };
1300 for o in o0..o1 {
1301 let dst_row_start = o * row_stride + dst_col_off;
1302 if broadcast_outer {
1303 if in_numel == 1 {
1304 if copy_per_row == 1 {
1305 out[dst_row_start] = inp[0];
1306 } else {
1307 out[dst_row_start..dst_row_start + copy_per_row].fill(inp[0]);
1308 }
1309 } else if copy_per_row <= inp.len() {
1310 out[dst_row_start..dst_row_start + copy_per_row]
1311 .copy_from_slice(&inp[..copy_per_row]);
1312 } else if !inp.is_empty() {
1313 out[dst_row_start..dst_row_start + copy_per_row].fill(inp[0]);
1314 }
1315 } else {
1316 let src_row_start = o * copy_per_row;
1317 out[dst_row_start..dst_row_start + copy_per_row]
1318 .copy_from_slice(&inp[src_row_start..src_row_start + copy_per_row]);
1319 }
1320 }
1321 };
1322 if outer >= 8
1324 && copy_per_row >= 16
1325 && crate::pool::num_threads() > 1
1326 && crate::pool::should_parallelize(outer.saturating_mul(copy_per_row))
1327 {
1328 crate::pool::par_for(outer, 1, &|off, cnt| run(off, off + cnt));
1329 } else {
1330 run(0, outer);
1331 }
1332 }
1333 };
1334}
1335
1336concat_copy_rows!(concat_copy_rows_f32, f32);
1337concat_copy_rows!(concat_copy_rows_f64, f64);
1338
1339pub(crate) fn is_trailing_bias_broadcast(
1357 rhs_dims: &[rlx_ir::Dim],
1358 out_dims: &[rlx_ir::Dim],
1359) -> bool {
1360 if rhs_dims.len() > out_dims.len() {
1361 return false;
1362 }
1363 let off = out_dims.len() - rhs_dims.len();
1364 for i in 0..rhs_dims.len() {
1365 let r = match rhs_dims[i] {
1366 rlx_ir::Dim::Static(n) => n,
1367 _ => return false,
1368 };
1369 let o = match out_dims[off + i] {
1370 rlx_ir::Dim::Static(n) => n,
1371 _ => return false,
1372 };
1373 if r != o {
1374 return false;
1375 }
1376 }
1377 true
1378}
1379
1380pub(crate) fn broadcast_strides(in_dims: &[usize], out_dims: &[usize]) -> Vec<u32> {
1381 let r_out = out_dims.len();
1382 let r_in = in_dims.len();
1383 assert!(
1384 r_in <= r_out,
1385 "broadcast: input rank {r_in} > output rank {r_out}"
1386 );
1387 let pad = r_out - r_in;
1388 let mut strides = vec![0u32; r_out];
1389 let mut acc: usize = 1;
1390 for d in (0..r_out).rev() {
1391 let in_size = if d < pad { 1 } else { in_dims[d - pad] };
1392 if in_size == 1 {
1393 strides[d] = 0;
1394 } else {
1395 assert_eq!(
1396 in_size, out_dims[d],
1397 "broadcast: input dim {in_size} doesn't match output dim {} at axis {d}",
1398 out_dims[d]
1399 );
1400 strides[d] = acc as u32;
1401 acc *= in_size;
1402 }
1403 }
1404 strides
1405}
1406
1407#[inline(always)]
1408pub(crate) fn exec_elementwise_region(t: &Thunk, base: *mut u8) {
1409 let Thunk::ElementwiseRegion {
1410 dst,
1411 len,
1412 input_offs,
1413 chain,
1414 scalar_input_mask,
1415 input_modulus,
1416 } = t
1417 else {
1418 unreachable!()
1419 };
1420 {
1421 let len = *len as usize;
1422 if !chain.is_empty() && len > 0 {
1423 let base_addr = base as usize;
1424 let dst = *dst;
1425 let scalar_mask = *scalar_input_mask;
1426 let eval = |gid: usize| {
1428 let v = region_eval_elem(
1429 gid,
1430 base_addr as *const u8,
1431 input_offs,
1432 chain,
1433 scalar_mask,
1434 input_modulus,
1435 );
1436 unsafe {
1437 *((base_addr as *mut u8).add(dst) as *mut f32).add(gid) = v;
1438 }
1439 };
1440 if fast_conv_enabled() && crate::pool::should_parallelize(len) {
1441 crate::pool::par_for(len, crate::pool::chunk_floor(len), &|off, cnt| {
1442 for gid in off..off + cnt {
1443 eval(gid);
1444 }
1445 });
1446 } else {
1447 for gid in 0..len {
1448 eval(gid);
1449 }
1450 }
1451 }
1452 }
1453}
1454
1455#[inline(always)]
1456pub(crate) fn exec_transpose_f64(t: &Thunk, base: *mut u8) {
1457 let Thunk::TransposeF64 {
1458 src,
1459 dst,
1460 in_total,
1461 out_dims,
1462 in_strides,
1463 } = t
1464 else {
1465 unreachable!()
1466 };
1467 unsafe {
1468 let inp = sl_f64(*src, base, *in_total as usize);
1469 let out_total: usize = out_dims.iter().map(|d| *d as usize).product();
1470 let out = sl_mut_f64(*dst, base, out_total);
1471 transpose_walk_f64(inp, out, out_dims, in_strides);
1472 }
1473}
1474
1475#[inline(always)]
1476pub(crate) fn exec_activation_f64(t: &Thunk, base: *mut u8) {
1477 let Thunk::ActivationF64 {
1478 src,
1479 dst,
1480 len,
1481 kind,
1482 } = t
1483 else {
1484 unreachable!()
1485 };
1486 {
1487 let len = *len as usize;
1488 unsafe {
1489 let inp = sl_f64(*src, base, len);
1490 let out = sl_mut_f64(*dst, base, len);
1491 apply_activation_f64(inp, out, *kind);
1492 }
1493 }
1494}
1495
1496#[inline(always)]
1497pub(crate) fn exec_binary_full_f64(t: &Thunk, base: *mut u8) {
1498 let Thunk::BinaryFullF64 {
1499 lhs,
1500 rhs,
1501 dst,
1502 len,
1503 lhs_len,
1504 rhs_len,
1505 op,
1506 out_dims_bcast,
1507 bcast_lhs_strides,
1508 bcast_rhs_strides,
1509 } = t
1510 else {
1511 unreachable!()
1512 };
1513 {
1514 let len = *len as usize;
1515 let lhs_len = *lhs_len as usize;
1516 let rhs_len = *rhs_len as usize;
1517 unsafe {
1518 let l = sl_f64(*lhs, base, lhs_len);
1519 let r = sl_f64(*rhs, base, rhs_len);
1520 let d = sl_mut_f64(*dst, base, len);
1521 if lhs_len == len && rhs_len == len {
1522 for i in 0..len {
1523 d[i] = binary_op_f64(*op, l[i], r[i]);
1524 }
1525 } else if !out_dims_bcast.is_empty() {
1526 let rank = out_dims_bcast.len();
1530 let mut coords = vec![0u32; rank];
1531 for i in 0..len {
1532 let mut rem = i;
1533 for ax in (0..rank).rev() {
1534 let sz = out_dims_bcast[ax] as usize;
1535 coords[ax] = (rem % sz) as u32;
1536 rem /= sz;
1537 }
1538 let mut li: usize = 0;
1539 let mut ri: usize = 0;
1540 for ax in 0..rank {
1541 li += coords[ax] as usize * bcast_lhs_strides[ax] as usize;
1542 ri += coords[ax] as usize * bcast_rhs_strides[ax] as usize;
1543 }
1544 d[i] = binary_op_f64(*op, l[li], r[ri]);
1545 }
1546 } else {
1547 for i in 0..len {
1552 d[i] = binary_op_f64(*op, l[i % lhs_len], r[i % rhs_len]);
1553 }
1554 }
1555 }
1556 }
1557}
1558
1559#[inline(always)]
1560pub(crate) fn exec_binary_full_c64(t: &Thunk, base: *mut u8) {
1561 let Thunk::BinaryFullC64 {
1562 lhs,
1563 rhs,
1564 dst,
1565 len,
1566 lhs_len,
1567 rhs_len,
1568 op,
1569 out_dims_bcast,
1570 bcast_lhs_strides,
1571 bcast_rhs_strides,
1572 } = t
1573 else {
1574 unreachable!()
1575 };
1576 {
1577 let n_out = *len as usize;
1583 let n_l = *lhs_len as usize;
1584 let n_r = *rhs_len as usize;
1585 unsafe {
1586 let l = sl(*lhs, base, 2 * n_l);
1587 let r = sl(*rhs, base, 2 * n_r);
1588 let d = sl_mut(*dst, base, 2 * n_out);
1589 let do_c64 = |a_re: f32, a_im: f32, b_re: f32, b_im: f32| -> (f32, f32) {
1590 match op {
1591 BinaryOp::Add => (a_re + b_re, a_im + b_im),
1592 BinaryOp::Sub => (a_re - b_re, a_im - b_im),
1593 BinaryOp::Mul => (a_re * b_re - a_im * b_im, a_re * b_im + a_im * b_re),
1594 BinaryOp::Div => {
1595 let denom = b_re * b_re + b_im * b_im;
1596 (
1597 (a_re * b_re + a_im * b_im) / denom,
1598 (a_im * b_re - a_re * b_im) / denom,
1599 )
1600 }
1601 BinaryOp::Max
1602 | BinaryOp::Min
1603 | BinaryOp::Pow
1604 | BinaryOp::Mod
1605 | BinaryOp::BitAnd
1606 | BinaryOp::BitOr
1607 | BinaryOp::BitXor
1608 | BinaryOp::Shl
1609 | BinaryOp::Shr
1610 | BinaryOp::Atan2 => {
1611 unreachable!("C64 max/min/pow/mod/bitwise/atan2 rejected at lowering")
1612 }
1613 }
1614 };
1615 if n_l == n_out && n_r == n_out {
1616 for i in 0..n_out {
1617 let (re, im) = do_c64(l[2 * i], l[2 * i + 1], r[2 * i], r[2 * i + 1]);
1618 d[2 * i] = re;
1619 d[2 * i + 1] = im;
1620 }
1621 } else if !out_dims_bcast.is_empty() {
1622 let rank = out_dims_bcast.len();
1626 let mut coords = vec![0u32; rank];
1627 for i in 0..n_out {
1628 let mut rem = i;
1629 for ax in (0..rank).rev() {
1630 let sz = out_dims_bcast[ax] as usize;
1631 coords[ax] = (rem % sz) as u32;
1632 rem /= sz;
1633 }
1634 let mut li: usize = 0;
1635 let mut ri: usize = 0;
1636 for ax in 0..rank {
1637 li += coords[ax] as usize * bcast_lhs_strides[ax] as usize;
1638 ri += coords[ax] as usize * bcast_rhs_strides[ax] as usize;
1639 }
1640 let (re, im) = do_c64(l[2 * li], l[2 * li + 1], r[2 * ri], r[2 * ri + 1]);
1641 d[2 * i] = re;
1642 d[2 * i + 1] = im;
1643 }
1644 } else {
1645 for i in 0..n_out {
1647 let li = if n_l == 1 { 0 } else { i % n_l };
1648 let ri = if n_r == 1 { 0 } else { i % n_r };
1649 let (re, im) = do_c64(l[2 * li], l[2 * li + 1], r[2 * ri], r[2 * ri + 1]);
1650 d[2 * i] = re;
1651 d[2 * i + 1] = im;
1652 }
1653 }
1654 }
1655 }
1656}
1657
1658#[inline(always)]
1659pub(crate) fn exec_activation_c64(t: &Thunk, base: *mut u8) {
1660 let Thunk::ActivationC64 {
1661 src,
1662 dst,
1663 len,
1664 kind,
1665 } = t
1666 else {
1667 unreachable!()
1668 };
1669 {
1670 let n = *len as usize;
1671 unsafe {
1672 let s = sl(*src, base, 2 * n);
1673 let d = sl_mut(*dst, base, 2 * n);
1674 for i in 0..n {
1675 let a = s[2 * i];
1676 let b = s[2 * i + 1];
1677 let (re, im) = match kind {
1678 Activation::Neg => (-a, -b),
1679 Activation::Exp => {
1680 let ea = a.exp();
1682 (ea * b.cos(), ea * b.sin())
1683 }
1684 Activation::Log => {
1685 let r = (a * a + b * b).sqrt();
1687 (r.ln(), b.atan2(a))
1688 }
1689 Activation::Sqrt => {
1690 let r = (a * a + b * b).sqrt();
1693 let re = ((r + a) * 0.5).max(0.0).sqrt();
1694 let im_mag = ((r - a) * 0.5).max(0.0).sqrt();
1695 let im = if b >= 0.0 { im_mag } else { -im_mag };
1696 (re, im)
1697 }
1698 _ => unreachable!("non-C64 activation kind survived lowering"),
1699 };
1700 d[2 * i] = re;
1701 d[2 * i + 1] = im;
1702 }
1703 }
1704 }
1705}
1706
1707#[inline(always)]
1708pub(crate) fn exec_gather(t: &Thunk, base: *mut u8) {
1709 let Thunk::Gather {
1710 table,
1711 table_len,
1712 idx,
1713 dst,
1714 num_idx,
1715 trailing,
1716 idx_i64,
1717 table_bytes,
1718 } = t
1719 else {
1720 unreachable!()
1721 };
1722 {
1723 let (ni, tr) = (*num_idx as usize, *trailing as usize);
1724 let rows = *table_len as usize / tr.max(1);
1725 unsafe {
1726 if *table_bytes == 8 {
1727 let tab = sl_i64(*table, base, *table_len as usize);
1728 let out = sl_mut_i64(*dst, base, ni * tr);
1729 if *idx_i64 != 0 {
1730 let ids = sl_i64(*idx, base, ni);
1731 for i in 0..ni {
1732 let row = ids[i].max(0) as usize;
1733 if row < rows {
1734 out[i * tr..(i + 1) * tr]
1735 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1736 }
1737 }
1738 } else {
1739 let ids = sl(*idx, base, ni);
1740 for i in 0..ni {
1741 let row = ids[i] as usize;
1742 if row < rows {
1743 out[i * tr..(i + 1) * tr]
1744 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1745 }
1746 }
1747 }
1748 } else {
1749 let tab = sl(*table, base, *table_len as usize);
1750 let out = sl_mut(*dst, base, ni * tr);
1751 if *idx_i64 != 0 {
1752 let ids = sl_i64(*idx, base, ni);
1753 for i in 0..ni {
1754 let row = ids[i].max(0) as usize;
1755 if row < rows {
1756 out[i * tr..(i + 1) * tr]
1757 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1758 }
1759 }
1760 } else {
1761 let ids = sl(*idx, base, ni);
1762 for i in 0..ni {
1763 let row = ids[i] as usize;
1764 if row < rows {
1765 out[i * tr..(i + 1) * tr]
1766 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1767 }
1768 }
1769 }
1770 }
1771 }
1772 }
1773}
1774
1775#[inline(always)]
1776pub(crate) fn exec_activation_in_place(t: &Thunk, base: *mut u8) {
1777 let Thunk::ActivationInPlace { data, len, act } = t else {
1778 unreachable!()
1779 };
1780 {
1781 let len = *len as usize;
1782 unsafe {
1783 let d = sl_mut(*data, base, len);
1784 let par = crate::pool::should_parallelize(len);
1790 macro_rules! apply {
1791 ($f:expr) => {{
1792 let f = $f;
1793 if par {
1794 use rayon::prelude::*;
1795 d.par_iter_mut().for_each(|v| *v = f(*v));
1796 } else {
1797 for v in d.iter_mut() {
1798 *v = f(*v);
1799 }
1800 }
1801 }};
1802 }
1803 match act {
1804 Activation::Gelu => crate::kernels::par_gelu_inplace(d),
1805 Activation::GeluApprox => crate::kernels::par_gelu_approx_inplace(d),
1806 Activation::Silu => crate::kernels::par_silu_inplace(d),
1807 Activation::Relu => apply!(|x: f32| x.max(0.0)),
1808 Activation::Sigmoid => apply!(|x: f32| 1.0 / (1.0 + (-x).exp())),
1809 Activation::Tanh => crate::vmath::vvtanhf_hot_inplace(d),
1810 Activation::Exp => crate::vmath::vvexpf_hot_inplace(d),
1811 Activation::Log => crate::vmath::vvlogf_inplace(d),
1812 Activation::Sqrt => crate::vmath::vvsqrtf_inplace(d),
1813 Activation::Rsqrt => crate::vmath::vvrsqrtf_inplace(d),
1814 Activation::Neg => apply!(|x: f32| -x),
1815 Activation::Abs => apply!(|x: f32| x.abs()),
1816 Activation::Round => apply!(|x: f32| x.round()),
1817 Activation::Sin => apply!(|x: f32| x.sin()),
1818 Activation::Cos => apply!(|x: f32| x.cos()),
1819 Activation::Tan => apply!(|x: f32| x.tan()),
1820 Activation::Atan => apply!(|x: f32| x.atan()),
1821 Activation::Recip => crate::vmath::vvrecf_inplace(d),
1822 Activation::Floor => apply!(|x: f32| x.floor()),
1823 Activation::Ceil => apply!(|x: f32| x.ceil()),
1824 Activation::Sign => {
1825 apply!(|x: f32| if x > 0.0 {
1826 1.0
1827 } else if x < 0.0 {
1828 -1.0
1829 } else {
1830 0.0
1831 })
1832 }
1833 Activation::Softplus => apply!(|x: f32| x.max(0.0) + (-(x.abs())).exp().ln_1p()),
1834 Activation::Elu => apply!(|x: f32| if x > 0.0 { x } else { x.exp() - 1.0 }),
1835 Activation::Erf => apply!(|x: f32| erf_f32(x)),
1836 Activation::HardSwish => apply!(|x: f32| x * (x + 3.0).clamp(0.0, 6.0) / 6.0),
1837 Activation::HardSigmoid => apply!(|x: f32| (x / 6.0 + 0.5).clamp(0.0, 1.0)),
1838 Activation::Mish => {
1839 apply!(|x: f32| x * (x.max(0.0) + (-(x.abs())).exp().ln_1p()).tanh())
1840 }
1841 Activation::Softsign => apply!(|x: f32| x / (1.0 + x.abs())),
1842 Activation::LogSigmoid => {
1843 apply!(|x: f32| x.min(0.0) - (-(x.abs())).exp().ln_1p())
1844 }
1845 }
1846 }
1847 }
1848}
1849
1850#[inline(always)]
1851pub(crate) fn exec_concat(t: &Thunk, base: *mut u8) {
1852 let Thunk::Concat {
1853 dst,
1854 outer,
1855 inner,
1856 total_axis,
1857 inputs,
1858 } = t
1859 else {
1860 unreachable!()
1861 };
1862 {
1863 let outer = *outer as usize;
1864 let inner = *inner as usize;
1865 let total_axis = *total_axis as usize;
1866 let row_stride = total_axis * inner;
1867 let out_total = outer * row_stride;
1868 unsafe {
1869 let out = sl_mut(*dst, base, out_total);
1870 let mut cum: usize = 0;
1871 for (src_off, in_axis, in_numel) in inputs {
1872 let in_axis = *in_axis as usize;
1873 let copy_per_row = in_axis * inner;
1874 let dst_col_off = cum * inner;
1875 let inp = sl(*src_off, base, (*in_numel as usize).max(1));
1876 concat_copy_rows_f32(
1877 out,
1878 inp,
1879 outer,
1880 copy_per_row,
1881 row_stride,
1882 dst_col_off,
1883 *in_numel as usize,
1884 );
1885 cum += in_axis;
1886 }
1887 }
1888 }
1889}
1890
1891#[inline(always)]
1892pub(crate) fn exec_concat_f64(t: &Thunk, base: *mut u8) {
1893 let Thunk::ConcatF64 {
1894 dst,
1895 outer,
1896 inner,
1897 total_axis,
1898 inputs,
1899 } = t
1900 else {
1901 unreachable!()
1902 };
1903 {
1904 let outer = *outer as usize;
1905 let inner = *inner as usize;
1906 let total_axis = *total_axis as usize;
1907 let row_stride = total_axis * inner;
1908 let out_total = outer * row_stride;
1909 unsafe {
1910 let out = sl_mut_f64(*dst, base, out_total);
1911 let mut cum: usize = 0;
1912 for (src_off, in_axis, in_numel) in inputs {
1913 let in_axis = *in_axis as usize;
1914 let copy_per_row = in_axis * inner;
1915 let dst_col_off = cum * inner;
1916 let inp = sl_f64(*src_off, base, (*in_numel as usize).max(1));
1917 concat_copy_rows_f64(
1918 out,
1919 inp,
1920 outer,
1921 copy_per_row,
1922 row_stride,
1923 dst_col_off,
1924 *in_numel as usize,
1925 );
1926 cum += in_axis;
1927 }
1928 }
1929 }
1930}
1931
1932#[inline(always)]
1933pub(crate) fn exec_scatter_add(t: &Thunk, base: *mut u8) {
1934 let Thunk::ScatterAdd {
1935 updates,
1936 indices,
1937 dst,
1938 num_updates,
1939 out_dim,
1940 trailing,
1941 } = t
1942 else {
1943 unreachable!()
1944 };
1945 {
1946 let num_updates = *num_updates as usize;
1947 let out_dim = *out_dim as usize;
1948 let trailing = *trailing as usize;
1949 unsafe {
1950 let upd = sl(*updates, base, num_updates * trailing);
1951 let ids = sl(*indices, base, num_updates);
1952 let out = sl_mut(*dst, base, out_dim * trailing);
1953 for v in out.iter_mut() {
1955 *v = 0.0;
1956 }
1957 for i in 0..num_updates {
1958 let row = ids[i] as usize;
1959 debug_assert!(row < out_dim, "ScatterAdd index out of range");
1960 let src_off = i * trailing;
1961 let dst_off = row * trailing;
1962 for j in 0..trailing {
1963 out[dst_off + j] += upd[src_off + j];
1964 }
1965 }
1966 }
1967 }
1968}
1969
1970#[inline(always)]
1971pub(crate) fn exec_scatter_nd(t: &Thunk, base: *mut u8) {
1972 let Thunk::ScatterNd {
1973 data,
1974 indices,
1975 updates,
1976 dst,
1977 data_shape,
1978 indices_shape,
1979 data_len,
1980 updates_len,
1981 indices_len,
1982 indices_i64,
1983 reduction,
1984 } = t
1985 else {
1986 unreachable!()
1987 };
1988 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
1989 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
1990 let data_len = *data_len as usize;
1991 let updates_len = *updates_len as usize;
1992 let indices_len = *indices_len as usize;
1993 unsafe {
1994 let data_s = sl(*data, base, data_len);
1995 let updates_s = sl(*updates, base, updates_len);
1996 let out = sl_mut(*dst, base, data_len);
1997 let idx_i64: Vec<i64> = if *indices_i64 != 0 {
1998 let ptr = base.add(*indices) as *const i64;
1999 std::slice::from_raw_parts(ptr, indices_len).to_vec()
2000 } else {
2001 sl(*indices, base, indices_len)
2002 .iter()
2003 .map(|&x| x as i64)
2004 .collect()
2005 };
2006 let (offsets, slice) =
2007 crate::onnx_indexing::scatter_nd_dst_offsets(&data_shape, &idx_i64, &indices_shape);
2008 crate::onnx_indexing::scatter_nd_into_f32(
2009 data_s, updates_s, out, &offsets, slice, *reduction,
2010 );
2011 }
2012}
2013
2014fn load_indices_i64(base: *mut u8, off: usize, len: usize, as_i64: u8) -> Vec<i64> {
2015 unsafe {
2016 if as_i64 != 0 {
2017 let ptr = base.add(off) as *const i64;
2018 std::slice::from_raw_parts(ptr, len).to_vec()
2019 } else {
2020 sl(off, base, len).iter().map(|&x| x as i64).collect()
2021 }
2022 }
2023}
2024
2025#[inline(always)]
2026pub(crate) fn exec_scatter_elements(t: &Thunk, base: *mut u8) {
2027 let Thunk::ScatterElements {
2028 data,
2029 indices,
2030 updates,
2031 dst,
2032 data_shape,
2033 data_len,
2034 updates_len,
2035 indices_len,
2036 indices_i64,
2037 axis,
2038 reduction,
2039 } = t
2040 else {
2041 unreachable!()
2042 };
2043 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2044 unsafe {
2045 let data_s = sl(*data, base, *data_len as usize);
2046 let updates_s = sl(*updates, base, *updates_len as usize);
2047 let out = sl_mut(*dst, base, *data_len as usize);
2048 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
2049 crate::onnx_indexing::scatter_elements_f32(
2050 data_s,
2051 updates_s,
2052 &idx,
2053 out,
2054 &data_shape,
2055 *axis,
2056 *reduction,
2057 );
2058 }
2059}
2060
2061#[inline(always)]
2062pub(crate) fn exec_gather_nd(t: &Thunk, base: *mut u8) {
2063 let Thunk::GatherNd {
2064 data,
2065 indices,
2066 dst,
2067 data_shape,
2068 indices_shape,
2069 data_len,
2070 indices_len,
2071 out_len,
2072 indices_i64,
2073 batch_dims,
2074 } = t
2075 else {
2076 unreachable!()
2077 };
2078 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2079 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2080 unsafe {
2081 let data_s = sl(*data, base, *data_len as usize);
2082 let out = sl_mut(*dst, base, *out_len as usize);
2083 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
2084 let (offsets, slice) = crate::onnx_indexing::gather_nd_src_offsets(
2085 &data_shape,
2086 &idx,
2087 &indices_shape,
2088 (*batch_dims).max(0) as usize,
2089 );
2090 for (t, &off) in offsets.iter().enumerate() {
2091 let d0 = t * slice;
2092 for j in 0..slice {
2093 if let (Some(&s), Some(d)) = (data_s.get(off + j), out.get_mut(d0 + j)) {
2094 *d = s;
2095 }
2096 }
2097 }
2098 }
2099}
2100
2101#[inline(always)]
2102pub(crate) fn exec_gather_elements(t: &Thunk, base: *mut u8) {
2103 let Thunk::GatherElements {
2104 data,
2105 indices,
2106 dst,
2107 data_shape,
2108 indices_shape,
2109 data_len,
2110 indices_len,
2111 out_len,
2112 indices_i64,
2113 data_elem_bytes,
2114 axis,
2115 } = t
2116 else {
2117 unreachable!()
2118 };
2119 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2120 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2121 let elem_bytes = (*data_elem_bytes as usize).max(1);
2122 unsafe {
2123 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
2124 if elem_bytes == 4 {
2125 let data_s = sl(*data, base, *data_len as usize);
2127 let out = sl_mut(*dst, base, *out_len as usize);
2128 crate::onnx_indexing::gather_elements_f32(
2129 data_s,
2130 &idx,
2131 out,
2132 &data_shape,
2133 &indices_shape,
2134 *axis,
2135 );
2136 } else {
2137 let data_b =
2139 std::slice::from_raw_parts(base.add(*data), *data_len as usize * elem_bytes);
2140 let out_b =
2141 std::slice::from_raw_parts_mut(base.add(*dst), *out_len as usize * elem_bytes);
2142 crate::onnx_indexing::gather_elements_bytes(
2143 data_b,
2144 &idx,
2145 out_b,
2146 &data_shape,
2147 &indices_shape,
2148 *axis,
2149 elem_bytes,
2150 );
2151 }
2152 }
2153}
2154
2155#[inline(always)]
2156pub(crate) fn exec_relu_backward(t: &Thunk, base: *mut u8) {
2157 let Thunk::ReluBackward { x, dy, dx, len } = t else {
2158 unreachable!()
2159 };
2160 {
2161 let len = *len as usize;
2162 unsafe {
2163 let xs = sl(*x, base, len);
2164 let dys = sl(*dy, base, len);
2165 let out = sl_mut(*dx, base, len);
2166 if fast_conv_enabled() && crate::pool::should_parallelize(len) {
2167 let oa = out.as_mut_ptr() as usize;
2168 crate::pool::par_for(len, crate::pool::chunk_floor(len), &|off, cnt| {
2169 for i in off..off + cnt {
2170 *((oa as *mut f32).add(i)) = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2171 }
2172 });
2173 } else {
2174 for i in 0..len {
2175 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2176 }
2177 }
2178 }
2179 }
2180}
2181
2182#[inline(always)]
2183pub(crate) fn exec_relu_backward_f64(t: &Thunk, base: *mut u8) {
2184 let Thunk::ReluBackwardF64 { x, dy, dx, len } = t else {
2185 unreachable!()
2186 };
2187 {
2188 let len = *len as usize;
2189 unsafe {
2190 let xs = sl_f64(*x, base, len);
2191 let dys = sl_f64(*dy, base, len);
2192 let out = sl_mut_f64(*dx, base, len);
2193 for i in 0..len {
2194 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2195 }
2196 }
2197 }
2198}
2199
2200#[inline(always)]
2201pub(crate) fn exec_activation_backward(t: &Thunk, base: *mut u8) {
2202 let Thunk::ActivationBackward {
2203 x,
2204 dy,
2205 dx,
2206 len,
2207 kind,
2208 } = t
2209 else {
2210 unreachable!()
2211 };
2212 {
2213 let len = *len as usize;
2214 unsafe {
2215 let xs = sl(*x, base, len);
2216 let dys = sl(*dy, base, len);
2217 let out = sl_mut(*dx, base, len);
2218 activation_backward_kernel(*kind, xs, dys, out);
2219 }
2220 }
2221}
2222
2223#[inline(always)]
2224pub(crate) fn exec_activation_backward_f64(t: &Thunk, base: *mut u8) {
2225 let Thunk::ActivationBackwardF64 {
2226 x,
2227 dy,
2228 dx,
2229 len,
2230 kind,
2231 } = t
2232 else {
2233 unreachable!()
2234 };
2235 {
2236 let len = *len as usize;
2237 unsafe {
2238 let xs = sl_f64(*x, base, len);
2239 let dys = sl_f64(*dy, base, len);
2240 let out = sl_mut_f64(*dx, base, len);
2241 activation_backward_kernel_f64(*kind, xs, dys, out);
2242 }
2243 }
2244}
2245
2246#[inline(always)]
2247pub(crate) fn exec_gather_backward(t: &Thunk, base: *mut u8) {
2248 let Thunk::GatherBackward {
2249 dy,
2250 indices,
2251 dst,
2252 outer,
2253 axis_dim,
2254 num_idx,
2255 trailing,
2256 } = t
2257 else {
2258 unreachable!()
2259 };
2260 {
2261 let (outer, axis_dim, num_idx, trailing) = (
2262 *outer as usize,
2263 *axis_dim as usize,
2264 *num_idx as usize,
2265 *trailing as usize,
2266 );
2267 unsafe {
2268 let dys = sl(*dy, base, outer * num_idx * trailing);
2269 let ids = sl(*indices, base, num_idx);
2270 let out = sl_mut(*dst, base, outer * axis_dim * trailing);
2271 for v in out.iter_mut() {
2272 *v = 0.0;
2273 }
2274 crate::training_bwd::gather_axis_backward(
2275 dys, ids, out, outer, axis_dim, num_idx, trailing,
2276 );
2277 }
2278 }
2279}
2280
2281#[inline(always)]
2282pub(crate) fn exec_gather_axis(t: &Thunk, base: *mut u8) {
2283 let Thunk::GatherAxis {
2284 table,
2285 idx,
2286 dst,
2287 outer,
2288 axis_dim,
2289 num_idx,
2290 trailing,
2291 idx_i64,
2292 table_bytes,
2293 } = t
2294 else {
2295 unreachable!()
2296 };
2297 {
2298 let outer = *outer as usize;
2299 let axis_dim = *axis_dim as usize;
2300 let num_idx = *num_idx as usize;
2301 let trailing = *trailing as usize;
2302 unsafe {
2303 if *table_bytes == 8 {
2304 let tab = sl_i64(*table, base, outer * axis_dim * trailing);
2305 let out = sl_mut_i64(*dst, base, outer * num_idx * trailing);
2306 for o in 0..outer {
2307 let tab_outer = o * axis_dim * trailing;
2308 let out_outer = o * num_idx * trailing;
2309 if *idx_i64 != 0 {
2310 let ids = sl_i64(*idx, base, num_idx);
2311 for k in 0..num_idx {
2312 let row = ids[k].max(0) as usize;
2313 if row < axis_dim {
2314 let tab_row = tab_outer + row * trailing;
2315 let out_row = out_outer + k * trailing;
2316 out[out_row..out_row + trailing]
2317 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2318 }
2319 }
2320 } else {
2321 let ids = sl(*idx, base, num_idx);
2322 for k in 0..num_idx {
2323 let row = ids[k] as usize;
2324 if row < axis_dim {
2325 let tab_row = tab_outer + row * trailing;
2326 let out_row = out_outer + k * trailing;
2327 out[out_row..out_row + trailing]
2328 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2329 }
2330 }
2331 }
2332 }
2333 } else {
2334 let tab = sl(*table, base, outer * axis_dim * trailing);
2335 let out = sl_mut(*dst, base, outer * num_idx * trailing);
2336 for o in 0..outer {
2337 let tab_outer = o * axis_dim * trailing;
2338 let out_outer = o * num_idx * trailing;
2339 if *idx_i64 != 0 {
2340 let ids = sl_i64(*idx, base, num_idx);
2341 for k in 0..num_idx {
2342 let row = ids[k].max(0) as usize;
2343 if row < axis_dim {
2344 let tab_row = tab_outer + row * trailing;
2345 let out_row = out_outer + k * trailing;
2346 out[out_row..out_row + trailing]
2347 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2348 }
2349 }
2350 } else {
2351 let ids = sl(*idx, base, num_idx);
2352 for k in 0..num_idx {
2353 let row = ids[k] as usize;
2354 if row < axis_dim {
2355 let tab_row = tab_outer + row * trailing;
2356 let out_row = out_outer + k * trailing;
2357 out[out_row..out_row + trailing]
2358 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2359 }
2360 }
2361 }
2362 }
2363 }
2364 }
2365 }
2366}
2367
2368#[inline(always)]
2369pub(crate) fn exec_reverse(t: &Thunk, base: *mut u8) {
2370 let Thunk::Reverse {
2371 src,
2372 dst,
2373 dims,
2374 rev_mask,
2375 elem_bytes,
2376 } = t
2377 else {
2378 unreachable!()
2379 };
2380 {
2381 let eb = *elem_bytes as usize;
2382 let rank = dims.len();
2383 let total: usize = dims.iter().map(|&d| d as usize).product::<usize>().max(1);
2384 let mut strides = vec![1usize; rank];
2385 for i in (0..rank.saturating_sub(1)).rev() {
2386 strides[i] = strides[i + 1] * dims[i + 1] as usize;
2387 }
2388 unsafe {
2389 let src_base = base.add(*src);
2390 let dst_base = base.add(*dst);
2391 for o in 0..total {
2392 let mut rem = o;
2393 let mut in_flat = 0usize;
2394 for ax in 0..rank {
2395 let idx = rem / strides[ax];
2396 rem %= strides[ax];
2397 let in_idx = if rev_mask[ax] {
2398 dims[ax] as usize - 1 - idx
2399 } else {
2400 idx
2401 };
2402 in_flat += in_idx * strides[ax];
2403 }
2404 std::ptr::copy_nonoverlapping(src_base.add(in_flat * eb), dst_base.add(o * eb), eb);
2405 }
2406 }
2407 }
2408}
2409
2410pub unsafe fn execute_reverse(
2416 src: usize,
2417 dst: usize,
2418 dims: &[u32],
2419 rev_mask: &[bool],
2420 elem_bytes: usize,
2421 base: *mut u8,
2422) {
2423 let rank = dims.len();
2424 let total: usize = dims.iter().map(|&d| d as usize).product::<usize>().max(1);
2425 let mut strides = vec![1usize; rank];
2426 for i in (0..rank.saturating_sub(1)).rev() {
2427 strides[i] = strides[i + 1] * dims[i + 1] as usize;
2428 }
2429 unsafe {
2430 let src_base = base.add(src);
2431 let dst_base = base.add(dst);
2432 for o in 0..total {
2433 let mut rem = o;
2434 let mut in_flat = 0usize;
2435 for ax in 0..rank {
2436 let idx = rem / strides[ax];
2437 rem %= strides[ax];
2438 let in_idx = if rev_mask[ax] {
2439 dims[ax] as usize - 1 - idx
2440 } else {
2441 idx
2442 };
2443 in_flat += in_idx * strides[ax];
2444 }
2445 std::ptr::copy_nonoverlapping(
2446 src_base.add(in_flat * elem_bytes),
2447 dst_base.add(o * elem_bytes),
2448 elem_bytes,
2449 );
2450 }
2451 }
2452}
2453
2454#[allow(clippy::too_many_arguments)]
2462pub unsafe fn execute_pad(
2463 src: usize,
2464 dst: usize,
2465 in_dims: &[u32],
2466 before: &[u32],
2467 after: &[u32],
2468 mode: rlx_ir::PadMode,
2469 fill: &[u8],
2470 elem_bytes: usize,
2471 base: *mut u8,
2472) {
2473 use rlx_ir::PadMode;
2474 let rank = in_dims.len();
2475 let in_dims: Vec<usize> = in_dims.iter().map(|&d| d as usize).collect();
2476 let out_dims: Vec<usize> = (0..rank)
2477 .map(|i| in_dims[i] + before[i] as usize + after[i] as usize)
2478 .collect();
2479 let total: usize = out_dims.iter().product::<usize>().max(1);
2480 let mut in_strides = vec![1usize; rank];
2481 let mut out_strides = vec![1usize; rank];
2482 for i in (0..rank.saturating_sub(1)).rev() {
2483 in_strides[i] = in_strides[i + 1] * in_dims[i + 1];
2484 out_strides[i] = out_strides[i + 1] * out_dims[i + 1];
2485 }
2486 unsafe {
2487 let src_base = base.add(src);
2488 let dst_base = base.add(dst);
2489 for o in 0..total {
2490 let mut rem = o;
2491 let mut in_flat = 0usize;
2492 let mut is_fill = false;
2493 for ax in 0..rank {
2494 let oc = (rem / out_strides[ax]) as isize;
2495 rem %= out_strides[ax];
2496 if before[ax] == 0 && after[ax] == 0 {
2498 in_flat += oc as usize * in_strides[ax];
2499 continue;
2500 }
2501 let n = in_dims[ax] as isize;
2502 let p = oc - before[ax] as isize;
2503 let ic: isize = match mode {
2504 PadMode::Constant(_) => {
2505 if p < 0 || p >= n {
2506 is_fill = true;
2507 break;
2508 }
2509 p
2510 }
2511 PadMode::Replicate => p.clamp(0, n - 1),
2512 PadMode::Circular => ((p % n) + n) % n,
2513 PadMode::Reflect => {
2514 let period = 2 * (n - 1);
2515 let mut i = ((p % period) + period) % period;
2516 if i >= n {
2517 i = period - i;
2518 }
2519 i
2520 }
2521 };
2522 in_flat += ic as usize * in_strides[ax];
2523 }
2524 let dptr = dst_base.add(o * elem_bytes);
2525 if is_fill {
2526 std::ptr::copy_nonoverlapping(fill.as_ptr(), dptr, elem_bytes);
2527 } else {
2528 std::ptr::copy_nonoverlapping(src_base.add(in_flat * elem_bytes), dptr, elem_bytes);
2529 }
2530 }
2531 }
2532}
2533
2534#[allow(clippy::too_many_arguments)]
2538pub unsafe fn execute_slice(
2539 src: usize,
2540 dst: usize,
2541 in_dims: &[u32],
2542 axis: usize,
2543 start: usize,
2544 len: usize,
2545 step: i64,
2546 elem_bytes: usize,
2547 base: *mut u8,
2548) {
2549 let rank = in_dims.len();
2550 let in_dims: Vec<usize> = in_dims.iter().map(|&d| d as usize).collect();
2551 let mut out_dims = in_dims.clone();
2552 out_dims[axis] = len;
2553 let total: usize = out_dims.iter().product::<usize>().max(1);
2554 let mut in_strides = vec![1usize; rank];
2555 let mut out_strides = vec![1usize; rank];
2556 for i in (0..rank.saturating_sub(1)).rev() {
2557 in_strides[i] = in_strides[i + 1] * in_dims[i + 1];
2558 out_strides[i] = out_strides[i + 1] * out_dims[i + 1];
2559 }
2560 unsafe {
2561 let src_base = base.add(src);
2562 let dst_base = base.add(dst);
2563 for o in 0..total {
2564 let mut rem = o;
2565 let mut in_flat = 0usize;
2566 for ax in 0..rank {
2567 let oc = rem / out_strides[ax];
2568 rem %= out_strides[ax];
2569 let ic = if ax == axis {
2570 (start as i64 + oc as i64 * step) as usize
2571 } else {
2572 oc
2573 };
2574 in_flat += ic * in_strides[ax];
2575 }
2576 std::ptr::copy_nonoverlapping(
2577 src_base.add(in_flat * elem_bytes),
2578 dst_base.add(o * elem_bytes),
2579 elem_bytes,
2580 );
2581 }
2582 }
2583}
2584
2585pub unsafe fn execute_gather_backward_f32(
2586 dy: usize,
2587 indices: usize,
2588 dst: usize,
2589 outer: u32,
2590 axis_dim: u32,
2591 num_idx: u32,
2592 trailing: u32,
2593 base: *mut u8,
2594) {
2595 let (outer, axis_dim, num_idx, trailing) = (
2596 outer as usize,
2597 axis_dim as usize,
2598 num_idx as usize,
2599 trailing as usize,
2600 );
2601 let out = sl_mut(dst, base, outer * axis_dim * trailing);
2602 out.fill(0.0);
2603 crate::training_bwd::gather_axis_backward(
2604 sl(dy, base, outer * num_idx * trailing),
2605 sl(indices, base, num_idx),
2606 out,
2607 outer,
2608 axis_dim,
2609 num_idx,
2610 trailing,
2611 );
2612}
2613
2614#[inline]
2617pub(crate) fn region_activation_scalar(act: rlx_ir::op::Activation, x: f32) -> f32 {
2618 use rlx_ir::op::Activation as A;
2619 const GC: f32 = 0.797_884_6; match act {
2621 A::Relu => x.max(0.0),
2622 A::Gelu | A::GeluApprox => 0.5 * x * (1.0 + (GC * (x + 0.044715 * x * x * x)).tanh()),
2623 A::Silu => x / (1.0 + (-x).exp()),
2624 A::Sigmoid => 1.0 / (1.0 + (-x).exp()),
2625 A::Tanh => x.tanh(),
2626 A::Exp => x.exp(),
2627 A::Log => x.ln(),
2628 A::Sqrt => x.sqrt(),
2629 A::Rsqrt => 1.0 / x.sqrt(),
2630 A::Neg => -x,
2631 A::Abs => x.abs(),
2632 A::Sin => x.sin(),
2633 A::Cos => x.cos(),
2634 A::Tan => x.tan(),
2635 A::Atan => x.atan(),
2636 A::Recip => 1.0 / x,
2637 A::Round => x.round(),
2638 A::Floor => x.floor(),
2639 A::Ceil => x.ceil(),
2640 A::Sign => {
2641 if x > 0.0 {
2642 1.0
2643 } else if x < 0.0 {
2644 -1.0
2645 } else {
2646 0.0
2647 }
2648 }
2649 A::Softplus => x.max(0.0) + (-(x.abs())).exp().ln_1p(),
2650 A::Elu => {
2651 if x > 0.0 {
2652 x
2653 } else {
2654 x.exp() - 1.0
2655 }
2656 }
2657 A::Erf => erf_f32(x),
2658 A::HardSwish => x * (x + 3.0).clamp(0.0, 6.0) / 6.0,
2659 A::HardSigmoid => (x / 6.0 + 0.5).clamp(0.0, 1.0),
2660 A::Mish => x * (x.max(0.0) + (-(x.abs())).exp().ln_1p()).tanh(),
2661 A::Softsign => x / (1.0 + x.abs()),
2662 A::LogSigmoid => x.min(0.0) - (-(x.abs())).exp().ln_1p(),
2663 }
2664}
2665
2666#[inline]
2667pub(crate) fn region_binary_scalar(op: rlx_ir::op::BinaryOp, l: f32, r: f32) -> f32 {
2668 use rlx_ir::op::BinaryOp as B;
2669 match op {
2670 B::Add => l + r,
2671 B::Sub => l - r,
2672 B::Mul => l * r,
2673 B::Div => l / r,
2674 B::Max => l.max(r),
2675 B::Min => l.min(r),
2676 B::Pow => l.powf(r),
2677 B::Mod => l % r,
2678 B::Atan2 => l.atan2(r),
2679 B::BitAnd => ((l as i64) & (r as i64)) as f32,
2680 B::BitOr => ((l as i64) | (r as i64)) as f32,
2681 B::BitXor => ((l as i64) ^ (r as i64)) as f32,
2682 B::Shl => (l as i64).wrapping_shl(r as u32) as f32,
2683 B::Shr => (l as i64).wrapping_shr(r as u32) as f32,
2684 }
2685}
2686
2687#[inline]
2688pub(crate) fn region_compare_scalar(op: rlx_ir::op::CmpOp, l: f32, r: f32) -> bool {
2689 use rlx_ir::op::CmpOp as C;
2690 match op {
2691 C::Eq => l == r,
2692 C::Ne => l != r,
2693 C::Lt => l < r,
2694 C::Le => l <= r,
2695 C::Gt => l > r,
2696 C::Ge => l >= r,
2697 }
2698}
2699
2700#[inline]
2704pub(crate) fn region_resolve_operand(
2705 op: &rlx_ir::op::ChainOperand,
2706 gid: usize,
2707 base: *const u8,
2708 input_offs: &[usize],
2709 scalar_mask: u32,
2710 modulus: &[u32; 16],
2711 scratch: &[f32; 32],
2712) -> f32 {
2713 use rlx_ir::op::ChainOperand as O;
2714 match op {
2715 O::Step(s) => scratch[*s as usize],
2716 O::Input(i) => {
2717 let i = *i as usize;
2718 let row = if (scalar_mask >> i) & 1 == 1 {
2719 0
2720 } else if modulus[i] != 0 {
2721 gid % modulus[i] as usize
2722 } else {
2723 gid
2724 };
2725 unsafe { *(base.add(input_offs[i]) as *const f32).add(row) }
2726 }
2727 }
2728}
2729
2730#[inline]
2733pub(crate) fn region_eval_elem(
2734 gid: usize,
2735 base: *const u8,
2736 input_offs: &[usize],
2737 chain: &[rlx_ir::op::ChainStep],
2738 scalar_mask: u32,
2739 modulus: &[u32; 16],
2740) -> f32 {
2741 use rlx_ir::op::ChainStep as S;
2742 let mut scratch = [0f32; 32];
2743 let r = |o: &rlx_ir::op::ChainOperand, sc: &[f32; 32]| {
2744 region_resolve_operand(o, gid, base, input_offs, scalar_mask, modulus, sc)
2745 };
2746 for (k, step) in chain.iter().enumerate() {
2747 scratch[k] = match step {
2748 S::Activation(a, x) => region_activation_scalar(*a, r(x, &scratch)),
2749 S::Cast(_, x) => r(x, &scratch), S::Binary(op, l, rr) => region_binary_scalar(*op, r(l, &scratch), r(rr, &scratch)),
2751 S::Compare(op, l, rr) => {
2752 if region_compare_scalar(*op, r(l, &scratch), r(rr, &scratch)) {
2753 1.0
2754 } else {
2755 0.0
2756 }
2757 }
2758 S::Where(c, t, f) => {
2759 if r(c, &scratch) != 0.0 {
2760 r(t, &scratch)
2761 } else {
2762 r(f, &scratch)
2763 }
2764 }
2765 };
2766 }
2767 scratch[chain.len() - 1]
2768}
2769
2770#[inline(always)]
2775pub(crate) fn apply_activation_inplace(d: &mut [f32], act: rlx_ir::op::Activation) {
2776 use rlx_ir::op::Activation;
2777 match act {
2778 Activation::Gelu => crate::kernels::par_gelu_inplace(d),
2779 Activation::GeluApprox => crate::kernels::par_gelu_approx_inplace(d),
2780 Activation::Silu => crate::kernels::par_silu_inplace(d),
2781 Activation::Relu => {
2782 for v in d.iter_mut() {
2783 *v = v.max(0.0);
2784 }
2785 }
2786 Activation::Sigmoid => {
2787 let x = d.to_vec();
2788 crate::vmath::vvsigmoidf(d, &x);
2789 }
2790 Activation::Tanh => crate::vmath::vvtanhf_hot_inplace(d),
2791 Activation::Exp => crate::vmath::vvexpf_hot_inplace(d),
2792 Activation::Log => crate::vmath::vvlogf_inplace(d),
2793 Activation::Sqrt => crate::vmath::vvsqrtf_inplace(d),
2794 Activation::Rsqrt => crate::vmath::vvrsqrtf_inplace(d),
2795 Activation::Neg => {
2796 for v in d.iter_mut() {
2797 *v = -*v;
2798 }
2799 }
2800 Activation::Abs => {
2801 for v in d.iter_mut() {
2802 *v = v.abs();
2803 }
2804 }
2805 Activation::Round => {
2806 for v in d.iter_mut() {
2807 *v = v.round();
2808 }
2809 }
2810 Activation::Sin => {
2811 for v in d.iter_mut() {
2812 *v = v.sin();
2813 }
2814 }
2815 Activation::Cos => {
2816 for v in d.iter_mut() {
2817 *v = v.cos();
2818 }
2819 }
2820 Activation::Tan => {
2821 for v in d.iter_mut() {
2822 *v = v.tan();
2823 }
2824 }
2825 Activation::Atan => {
2826 for v in d.iter_mut() {
2827 *v = v.atan();
2828 }
2829 }
2830 Activation::Recip => crate::vmath::vvrecf_inplace(d),
2831 Activation::Floor => {
2832 for v in d.iter_mut() {
2833 *v = v.floor();
2834 }
2835 }
2836 Activation::Ceil => {
2837 for v in d.iter_mut() {
2838 *v = v.ceil();
2839 }
2840 }
2841 Activation::Sign => {
2842 for v in d.iter_mut() {
2843 *v = if *v > 0.0 {
2844 1.0
2845 } else if *v < 0.0 {
2846 -1.0
2847 } else {
2848 0.0
2849 };
2850 }
2851 }
2852 Activation::Softplus => {
2853 for v in d.iter_mut() {
2854 *v = v.max(0.0) + (-(v.abs())).exp().ln_1p();
2855 }
2856 }
2857 Activation::Elu => {
2858 for v in d.iter_mut() {
2859 *v = if *v > 0.0 { *v } else { v.exp() - 1.0 };
2860 }
2861 }
2862 Activation::Erf => {
2863 for v in d.iter_mut() {
2864 *v = erf_f32(*v);
2865 }
2866 }
2867 Activation::HardSwish => {
2868 for v in d.iter_mut() {
2869 let x = *v;
2870 *v = x * (x + 3.0).clamp(0.0, 6.0) / 6.0;
2871 }
2872 }
2873 Activation::HardSigmoid => {
2874 for v in d.iter_mut() {
2875 *v = (*v / 6.0 + 0.5).clamp(0.0, 1.0);
2876 }
2877 }
2878 Activation::Mish => {
2879 for v in d.iter_mut() {
2880 let x = *v;
2881 *v = x * (x.max(0.0) + (-(x.abs())).exp().ln_1p()).tanh();
2882 }
2883 }
2884 Activation::Softsign => {
2885 for v in d.iter_mut() {
2886 let x = *v;
2887 *v = x / (1.0 + x.abs());
2888 }
2889 }
2890 Activation::LogSigmoid => {
2891 for v in d.iter_mut() {
2892 let x = *v;
2893 *v = x.min(0.0) - (-(x.abs())).exp().ln_1p();
2894 }
2895 }
2896 }
2897}
2898
2899pub(crate) fn activation_backward_kernel(
2900 act: rlx_ir::op::Activation,
2901 xs: &[f32],
2902 dys: &[f32],
2903 out: &mut [f32],
2904) {
2905 use rlx_ir::op::Activation;
2906 let n = xs.len();
2907 debug_assert_eq!(dys.len(), n);
2908 debug_assert_eq!(out.len(), n);
2909 match act {
2910 Activation::Relu => {
2911 for i in 0..n {
2912 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2913 }
2914 }
2915 Activation::Sigmoid => {
2916 for i in 0..n {
2917 let s = 1.0 / (1.0 + (-xs[i]).exp());
2918 out[i] = s * (1.0 - s) * dys[i];
2919 }
2920 }
2921 Activation::Tanh => {
2922 for i in 0..n {
2923 let t = xs[i].tanh();
2924 out[i] = (1.0 - t * t) * dys[i];
2925 }
2926 }
2927 Activation::Silu => {
2928 for i in 0..n {
2930 let s = 1.0 / (1.0 + (-xs[i]).exp());
2931 out[i] = s * (1.0 + xs[i] * (1.0 - s)) * dys[i];
2932 }
2933 }
2934 Activation::Gelu => {
2935 const INV_SQRT2: f32 = 0.707_106_77;
2938 const INV_SQRT_2PI: f32 = 0.398_942_3;
2939 for i in 0..n {
2940 let x = xs[i];
2941 let phi = 0.5 * (1.0 + erf_f32(x * INV_SQRT2));
2942 let pdf = INV_SQRT_2PI * (-(x * x) * 0.5).exp();
2943 out[i] = (phi + x * pdf) * dys[i];
2944 }
2945 }
2946 Activation::GeluApprox => {
2947 const C: f32 = 0.797_884_6; const A: f32 = 0.044_715;
2951 for i in 0..n {
2952 let x = xs[i];
2953 let inner = C * (x + A * x * x * x);
2954 let t = inner.tanh();
2955 let dinner = C * (1.0 + 3.0 * A * x * x);
2956 let d = 0.5 * (1.0 + t) + 0.5 * x * (1.0 - t * t) * dinner;
2957 out[i] = d * dys[i];
2958 }
2959 }
2960 Activation::Exp => {
2961 for i in 0..n {
2962 out[i] = xs[i].exp() * dys[i];
2963 }
2964 }
2965 Activation::Log => {
2966 for i in 0..n {
2967 out[i] = dys[i] / xs[i];
2968 }
2969 }
2970 Activation::Sqrt => {
2971 for i in 0..n {
2973 let s = xs[i].sqrt();
2974 out[i] = if s > 0.0 { 0.5 * dys[i] / s } else { 0.0 };
2975 }
2976 }
2977 Activation::Rsqrt => {
2978 for i in 0..n {
2980 let s = xs[i].sqrt();
2981 out[i] = if s > 0.0 {
2982 -0.5 * dys[i] / (xs[i] * s)
2983 } else {
2984 0.0
2985 };
2986 }
2987 }
2988 Activation::Neg => {
2989 for i in 0..n {
2990 out[i] = -dys[i];
2991 }
2992 }
2993 Activation::Abs => {
2994 for i in 0..n {
2996 let x = xs[i];
2997 let s = if x > 0.0 {
2998 1.0
2999 } else if x < 0.0 {
3000 -1.0
3001 } else {
3002 0.0
3003 };
3004 out[i] = s * dys[i];
3005 }
3006 }
3007 Activation::Round => {
3008 out.copy_from_slice(dys);
3013 }
3014 Activation::Sin => {
3015 for i in 0..n {
3017 out[i] = xs[i].cos() * dys[i];
3018 }
3019 }
3020 Activation::Cos => {
3021 for i in 0..n {
3022 out[i] = -xs[i].sin() * dys[i];
3023 }
3024 }
3025 Activation::Tan => {
3026 for i in 0..n {
3028 let t = xs[i].tan();
3029 out[i] = (1.0 + t * t) * dys[i];
3030 }
3031 }
3032 Activation::Atan => {
3033 for i in 0..n {
3035 let x = xs[i];
3036 out[i] = dys[i] / (1.0 + x * x);
3037 }
3038 }
3039 Activation::Recip => {
3040 for i in 0..n {
3041 out[i] = -dys[i] / (xs[i] * xs[i]);
3042 }
3043 }
3044 Activation::Floor | Activation::Ceil | Activation::Sign => {
3046 for o in out.iter_mut() {
3047 *o = 0.0;
3048 }
3049 }
3050 Activation::Softplus => {
3051 for i in 0..n {
3052 out[i] = dys[i] / (1.0 + (-xs[i]).exp());
3053 }
3054 }
3055 Activation::Elu => {
3056 for i in 0..n {
3057 out[i] = if xs[i] > 0.0 {
3058 dys[i]
3059 } else {
3060 dys[i] * xs[i].exp()
3061 };
3062 }
3063 }
3064 Activation::Erf
3066 | Activation::HardSwish
3067 | Activation::HardSigmoid
3068 | Activation::Mish
3069 | Activation::Softsign
3070 | Activation::LogSigmoid => {
3071 panic!("activation_backward_kernel: {act:?} is decomposed at the AD level")
3072 }
3073 }
3074}
3075
3076pub(crate) fn activation_backward_kernel_f64(
3080 act: rlx_ir::op::Activation,
3081 xs: &[f64],
3082 dys: &[f64],
3083 out: &mut [f64],
3084) {
3085 use rlx_ir::op::Activation;
3086 let n = xs.len();
3087 debug_assert_eq!(dys.len(), n);
3088 debug_assert_eq!(out.len(), n);
3089 match act {
3090 Activation::Relu => {
3091 for i in 0..n {
3092 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
3093 }
3094 }
3095 Activation::Sigmoid => {
3096 for i in 0..n {
3097 let s = 1.0 / (1.0 + (-xs[i]).exp());
3098 out[i] = s * (1.0 - s) * dys[i];
3099 }
3100 }
3101 Activation::Tanh => {
3102 for i in 0..n {
3103 let t = xs[i].tanh();
3104 out[i] = (1.0 - t * t) * dys[i];
3105 }
3106 }
3107 Activation::Silu => {
3108 for i in 0..n {
3109 let s = 1.0 / (1.0 + (-xs[i]).exp());
3110 out[i] = s * (1.0 + xs[i] * (1.0 - s)) * dys[i];
3111 }
3112 }
3113 Activation::Gelu | Activation::GeluApprox => {
3114 const INV_SQRT2: f64 = std::f64::consts::FRAC_1_SQRT_2;
3116 const INV_SQRT_2PI: f64 = 0.398_942_280_401_432_7;
3117 for i in 0..n {
3118 let x = xs[i];
3119 let phi = 0.5 * (1.0 + erf_f64(x * INV_SQRT2));
3120 let pdf = INV_SQRT_2PI * (-(x * x) * 0.5).exp();
3121 out[i] = (phi + x * pdf) * dys[i];
3122 }
3123 }
3124 Activation::Exp => {
3125 for i in 0..n {
3126 out[i] = xs[i].exp() * dys[i];
3127 }
3128 }
3129 Activation::Log => {
3130 for i in 0..n {
3131 out[i] = dys[i] / xs[i];
3132 }
3133 }
3134 Activation::Sqrt => {
3135 for i in 0..n {
3136 let s = xs[i].sqrt();
3137 out[i] = if s > 0.0 { 0.5 * dys[i] / s } else { 0.0 };
3138 }
3139 }
3140 Activation::Rsqrt => {
3141 for i in 0..n {
3142 let s = xs[i].sqrt();
3143 out[i] = if s > 0.0 {
3144 -0.5 * dys[i] / (xs[i] * s)
3145 } else {
3146 0.0
3147 };
3148 }
3149 }
3150 Activation::Neg => {
3151 for i in 0..n {
3152 out[i] = -dys[i];
3153 }
3154 }
3155 Activation::Abs => {
3156 for i in 0..n {
3157 let x = xs[i];
3158 let s = if x > 0.0 {
3159 1.0
3160 } else if x < 0.0 {
3161 -1.0
3162 } else {
3163 0.0
3164 };
3165 out[i] = s * dys[i];
3166 }
3167 }
3168 Activation::Round => {
3169 out.copy_from_slice(dys);
3170 }
3171 Activation::Sin => {
3172 for i in 0..n {
3173 out[i] = xs[i].cos() * dys[i];
3174 }
3175 }
3176 Activation::Cos => {
3177 for i in 0..n {
3178 out[i] = -xs[i].sin() * dys[i];
3179 }
3180 }
3181 Activation::Tan => {
3182 for i in 0..n {
3183 let t = xs[i].tan();
3184 out[i] = (1.0 + t * t) * dys[i];
3185 }
3186 }
3187 Activation::Atan => {
3188 for i in 0..n {
3189 let x = xs[i];
3190 out[i] = dys[i] / (1.0 + x * x);
3191 }
3192 }
3193 Activation::Recip => {
3194 for i in 0..n {
3195 out[i] = -dys[i] / (xs[i] * xs[i]);
3196 }
3197 }
3198 Activation::Floor | Activation::Ceil | Activation::Sign => {
3200 for o in out.iter_mut() {
3201 *o = 0.0;
3202 }
3203 }
3204 Activation::Softplus => {
3205 for i in 0..n {
3206 out[i] = dys[i] / (1.0 + (-xs[i]).exp());
3207 }
3208 }
3209 Activation::Elu => {
3210 for i in 0..n {
3211 out[i] = if xs[i] > 0.0 {
3212 dys[i]
3213 } else {
3214 dys[i] * xs[i].exp()
3215 };
3216 }
3217 }
3218 Activation::Erf
3220 | Activation::HardSwish
3221 | Activation::HardSigmoid
3222 | Activation::Mish
3223 | Activation::Softsign
3224 | Activation::LogSigmoid => {
3225 panic!("activation_backward_kernel: {act:?} is decomposed at the AD level")
3226 }
3227 }
3228}
3229
3230#[inline(always)]
3235pub(crate) fn erf_f64(x: f64) -> f64 {
3236 let s = x.signum();
3237 let x = x.abs();
3238 let t = 1.0 / (1.0 + 0.327_591_1 * x);
3239 let y = 1.0
3240 - (((((1.061_405_43 * t - 1.453_152_03) * t) + 1.421_413_75) * t - 0.284_496_74) * t
3241 + 0.254_829_59)
3242 * t
3243 * (-x * x).exp();
3244 s * y
3245}
3246
3247#[inline(always)]
3250pub(crate) fn erf_f32(x: f32) -> f32 {
3251 let s = x.signum();
3252 let x = x.abs();
3253 let t = 1.0 / (1.0 + 0.327_591_1 * x);
3254 let y = 1.0
3255 - (((((1.061_405_4 * t - 1.453_152_1) * t) + 1.421_413_8) * t - 0.284_496_74) * t
3256 + 0.254_829_6)
3257 * t
3258 * (-x * x).exp();
3259 s * y
3260}
3261
3262pub(crate) fn narrow_thunk_closure(
3263 src: usize,
3264 dst: usize,
3265 outer: u32,
3266 src_stride: u32,
3267 dst_stride: u32,
3268 inner: u32,
3269 elem_bytes: u8,
3270) -> Arc<dyn Fn(*mut u8) + Send + Sync> {
3271 let (outer, ss, ds, inner, eb) = (
3272 outer as usize,
3273 src_stride as usize,
3274 dst_stride as usize,
3275 inner as usize,
3276 elem_bytes as usize,
3277 );
3278 let row_bytes = inner.saturating_mul(eb);
3279 let src_row_stride = ss.saturating_mul(eb);
3280 let dst_row_stride = ds.saturating_mul(eb);
3281 Arc::new(move |base: *mut u8| unsafe {
3282 if row_bytes == 0 || src == dst {
3283 return;
3284 }
3285 let arena_len = usize::MAX;
3287 for o in 0..outer {
3288 let s_off = src + o * src_row_stride;
3289 let d_off = dst + o * dst_row_stride;
3290 if s_off == d_off {
3291 continue;
3292 }
3293 if s_off.saturating_add(row_bytes) > arena_len
3294 || d_off.saturating_add(row_bytes) > arena_len
3295 {
3296 break;
3297 }
3298 std::ptr::copy_nonoverlapping(base.add(s_off), base.add(d_off), row_bytes);
3299 }
3300 })
3301}
3302
3303pub(crate) fn transpose_walk_f64(
3307 inp: &[f64],
3308 out: &mut [f64],
3309 out_dims: &[u32],
3310 in_strides: &[u32],
3311) {
3312 let rank = out_dims.len();
3313 let mut idx = vec![0u32; rank];
3314 for o in 0..out.len() {
3315 let mut src_off = 0usize;
3316 for d in 0..rank {
3317 src_off += idx[d] as usize * in_strides[d] as usize;
3318 }
3319 out[o] = inp[broadcast_src_index(src_off, inp.len())];
3320 for d in (0..rank).rev() {
3322 idx[d] += 1;
3323 if idx[d] < out_dims[d] {
3324 break;
3325 }
3326 idx[d] = 0;
3327 }
3328 }
3329}
3330
3331pub(crate) fn apply_activation_f64(inp: &[f64], out: &mut [f64], kind: Activation) {
3337 match kind {
3338 Activation::Neg => {
3339 for (o, &v) in out.iter_mut().zip(inp) {
3340 *o = -v;
3341 }
3342 }
3343 Activation::Exp => {
3344 for (o, &v) in out.iter_mut().zip(inp) {
3345 *o = v.exp();
3346 }
3347 }
3348 Activation::Log => {
3349 for (o, &v) in out.iter_mut().zip(inp) {
3350 *o = v.ln();
3351 }
3352 }
3353 Activation::Sqrt => {
3354 for (o, &v) in out.iter_mut().zip(inp) {
3355 *o = v.sqrt();
3356 }
3357 }
3358 Activation::Rsqrt => {
3359 for (o, &v) in out.iter_mut().zip(inp) {
3360 *o = 1.0 / v.sqrt();
3361 }
3362 }
3363 Activation::Abs => {
3364 for (o, &v) in out.iter_mut().zip(inp) {
3365 *o = v.abs();
3366 }
3367 }
3368 Activation::Tanh => {
3369 for (o, &v) in out.iter_mut().zip(inp) {
3370 *o = v.tanh();
3371 }
3372 }
3373 Activation::Sigmoid => {
3374 for (o, &v) in out.iter_mut().zip(inp) {
3375 *o = 1.0 / (1.0 + (-v).exp());
3376 }
3377 }
3378 Activation::Relu => {
3379 for (o, &v) in out.iter_mut().zip(inp) {
3380 *o = v.max(0.0);
3381 }
3382 }
3383 Activation::Round => {
3384 for (o, &v) in out.iter_mut().zip(inp) {
3385 *o = v.round_ties_even();
3386 }
3387 }
3388 Activation::Sin => {
3389 for (o, &v) in out.iter_mut().zip(inp) {
3390 *o = v.sin();
3391 }
3392 }
3393 Activation::Cos => {
3394 for (o, &v) in out.iter_mut().zip(inp) {
3395 *o = v.cos();
3396 }
3397 }
3398 Activation::Tan => {
3399 for (o, &v) in out.iter_mut().zip(inp) {
3400 *o = v.tan();
3401 }
3402 }
3403 Activation::Atan => {
3404 for (o, &v) in out.iter_mut().zip(inp) {
3405 *o = v.atan();
3406 }
3407 }
3408 Activation::Recip => {
3409 for (o, &v) in out.iter_mut().zip(inp) {
3410 *o = 1.0 / v;
3411 }
3412 }
3413 Activation::Floor => {
3414 for (o, &v) in out.iter_mut().zip(inp) {
3415 *o = v.floor();
3416 }
3417 }
3418 Activation::Ceil => {
3419 for (o, &v) in out.iter_mut().zip(inp) {
3420 *o = v.ceil();
3421 }
3422 }
3423 Activation::Sign => {
3424 for (o, &v) in out.iter_mut().zip(inp) {
3425 *o = if v > 0.0 {
3426 1.0
3427 } else if v < 0.0 {
3428 -1.0
3429 } else {
3430 0.0
3431 };
3432 }
3433 }
3434 Activation::Softplus => {
3435 for (o, &v) in out.iter_mut().zip(inp) {
3436 *o = v.max(0.0) + (-(v.abs())).exp().ln_1p();
3437 }
3438 }
3439 Activation::Elu => {
3440 for (o, &v) in out.iter_mut().zip(inp) {
3441 *o = if v > 0.0 { v } else { v.exp() - 1.0 };
3442 }
3443 }
3444 Activation::Erf => {
3445 for (o, &v) in out.iter_mut().zip(inp) {
3446 *o = erf_f64(v);
3447 }
3448 }
3449 Activation::HardSwish => {
3450 for (o, &v) in out.iter_mut().zip(inp) {
3451 *o = v * (v + 3.0).clamp(0.0, 6.0) / 6.0;
3452 }
3453 }
3454 Activation::HardSigmoid => {
3455 for (o, &v) in out.iter_mut().zip(inp) {
3456 *o = (v / 6.0 + 0.5).clamp(0.0, 1.0);
3457 }
3458 }
3459 Activation::Mish => {
3460 for (o, &v) in out.iter_mut().zip(inp) {
3461 *o = v * (v.max(0.0) + (-(v.abs())).exp().ln_1p()).tanh();
3462 }
3463 }
3464 Activation::Softsign => {
3465 for (o, &v) in out.iter_mut().zip(inp) {
3466 *o = v / (1.0 + v.abs());
3467 }
3468 }
3469 Activation::LogSigmoid => {
3470 for (o, &v) in out.iter_mut().zip(inp) {
3471 *o = v.min(0.0) - (-(v.abs())).exp().ln_1p();
3472 }
3473 }
3474 Activation::Gelu | Activation::GeluApprox | Activation::Silu => {
3475 panic!(
3476 "apply_activation_f64: {kind:?} not yet implemented at f64. \
3477 Add when a workload needs it."
3478 );
3479 }
3480 }
3481}
3482
3483#[inline]
3484pub(crate) fn binary_op_f64(op: BinaryOp, a: f64, b: f64) -> f64 {
3485 match op {
3486 BinaryOp::Add => a + b,
3487 BinaryOp::Sub => a - b,
3488 BinaryOp::Mul => a * b,
3489 BinaryOp::Div => a / b,
3490 BinaryOp::Max => a.max(b),
3491 BinaryOp::Min => a.min(b),
3492 BinaryOp::Pow => a.powf(b),
3493 BinaryOp::Mod => a % b,
3494 BinaryOp::Atan2 => a.atan2(b),
3495 BinaryOp::BitAnd => ((a as i64) & (b as i64)) as f64,
3496 BinaryOp::BitOr => ((a as i64) | (b as i64)) as f64,
3497 BinaryOp::BitXor => ((a as i64) ^ (b as i64)) as f64,
3498 BinaryOp::Shl => ((a as i64) << (b as i64)) as f64,
3499 BinaryOp::Shr => ((a as i64) >> (b as i64)) as f64,
3500 }
3501}