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 { axis } = &node.op else {
732 unreachable!()
733 };
734 assert_eq!(
739 *axis, 0,
740 "rlx-cpu: ScatterAdd axis {axis} reached the backend; run LowerScatterAddAxis first"
741 );
742 {
743 let upd_shape = &graph.node(node.inputs[0]).shape;
746 let out_shape = &node.shape;
747 let num_updates = upd_shape.dim(0).unwrap_static();
748 let out_dim = out_shape.dim(0).unwrap_static();
749 let trailing: usize = (1..out_shape.rank())
750 .map(|i| out_shape.dim(i).unwrap_static())
751 .product::<usize>()
752 .max(1);
753 Thunk::ScatterAdd {
754 updates: node_offset(arena, node.inputs[0]),
755 indices: node_offset(arena, node.inputs[1]),
756 dst: node_offset(arena, node.id),
757 num_updates: num_updates as u32,
758 out_dim: out_dim as u32,
759 trailing: trailing as u32,
760 }
761 }
762}
763
764#[allow(unused_variables)]
765pub(crate) fn compile_scatter_nd(
766 node: &rlx_ir::Node,
767 graph: &Graph,
768 arena: &crate::arena::Arena,
769 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
770 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
771 rng: rlx_ir::RngOptions,
772) -> Thunk {
773 let Op::ScatterNd { reduction } = &node.op else {
774 unreachable!()
775 };
776 let data_shape = &graph.node(node.inputs[0]).shape;
777 let indices_shape = &graph.node(node.inputs[1]).shape;
778 let updates_shape = &graph.node(node.inputs[2]).shape;
779 let data_len = data_shape.num_elements().unwrap_or(0);
780 let updates_len = updates_shape.num_elements().unwrap_or(0);
781 let indices_len = indices_shape.num_elements().unwrap_or(0);
782 let indices_i64 = u8::from(indices_shape.dtype() == rlx_ir::DType::I64);
783 Thunk::ScatterNd {
784 data: node_offset(arena, node.inputs[0]),
785 indices: node_offset(arena, node.inputs[1]),
786 updates: node_offset(arena, node.inputs[2]),
787 dst: node_offset(arena, node.id),
788 data_shape: (0..data_shape.rank())
789 .map(|i| data_shape.dim(i).unwrap_static() as u32)
790 .collect(),
791 indices_shape: (0..indices_shape.rank())
792 .map(|i| indices_shape.dim(i).unwrap_static() as u32)
793 .collect(),
794 data_len: data_len as u32,
795 updates_len: updates_len as u32,
796 indices_len: indices_len as u32,
797 indices_i64,
798 reduction: *reduction,
799 }
800}
801
802fn shape_u32s(shape: &rlx_ir::Shape) -> Vec<u32> {
803 (0..shape.rank())
804 .map(|i| shape.dim(i).unwrap_static() as u32)
805 .collect()
806}
807
808#[allow(unused_variables)]
809pub(crate) fn compile_scatter_elements(
810 node: &rlx_ir::Node,
811 graph: &Graph,
812 arena: &crate::arena::Arena,
813 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
814 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
815 rng: rlx_ir::RngOptions,
816) -> Thunk {
817 let Op::ScatterElements { axis, reduction } = &node.op else {
818 unreachable!()
819 };
820 let data_shape = &graph.node(node.inputs[0]).shape;
821 let indices_shape = &graph.node(node.inputs[1]).shape;
822 let updates_shape = &graph.node(node.inputs[2]).shape;
823 Thunk::ScatterElements {
824 data: node_offset(arena, node.inputs[0]),
825 indices: node_offset(arena, node.inputs[1]),
826 updates: node_offset(arena, node.inputs[2]),
827 dst: node_offset(arena, node.id),
828 data_shape: shape_u32s(data_shape),
829 indices_shape: shape_u32s(indices_shape),
830 data_len: data_shape.num_elements().unwrap_or(0) as u32,
831 updates_len: updates_shape.num_elements().unwrap_or(0) as u32,
832 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
833 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
834 axis: *axis,
835 reduction: *reduction,
836 }
837}
838
839#[allow(unused_variables)]
840pub(crate) fn compile_gather_nd(
841 node: &rlx_ir::Node,
842 graph: &Graph,
843 arena: &crate::arena::Arena,
844 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
845 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
846 rng: rlx_ir::RngOptions,
847) -> Thunk {
848 let Op::GatherNd { batch_dims } = &node.op else {
849 unreachable!()
850 };
851 let data_shape = &graph.node(node.inputs[0]).shape;
852 let indices_shape = &graph.node(node.inputs[1]).shape;
853 Thunk::GatherNd {
854 data: node_offset(arena, node.inputs[0]),
855 indices: node_offset(arena, node.inputs[1]),
856 dst: node_offset(arena, node.id),
857 data_shape: shape_u32s(data_shape),
858 indices_shape: shape_u32s(indices_shape),
859 data_len: data_shape.num_elements().unwrap_or(0) as u32,
860 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
861 out_len: node.shape.num_elements().unwrap_or(0) as u32,
862 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
863 batch_dims: *batch_dims,
864 }
865}
866
867#[allow(unused_variables)]
868pub(crate) fn compile_gather_elements(
869 node: &rlx_ir::Node,
870 graph: &Graph,
871 arena: &crate::arena::Arena,
872 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
873 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
874 rng: rlx_ir::RngOptions,
875) -> Thunk {
876 let Op::GatherElements { axis } = &node.op else {
877 unreachable!()
878 };
879 let data_shape = &graph.node(node.inputs[0]).shape;
880 let indices_shape = &graph.node(node.inputs[1]).shape;
881 Thunk::GatherElements {
882 data: node_offset(arena, node.inputs[0]),
883 indices: node_offset(arena, node.inputs[1]),
884 dst: node_offset(arena, node.id),
885 data_shape: shape_u32s(data_shape),
886 indices_shape: shape_u32s(indices_shape),
887 data_len: data_shape.num_elements().unwrap_or(0) as u32,
888 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
889 out_len: node.shape.num_elements().unwrap_or(0) as u32,
890 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
891 data_elem_bytes: data_shape.dtype().size_bytes() as u8,
892 axis: *axis,
893 }
894}
895
896#[allow(unused_variables)]
897pub(crate) fn compile_compare(
898 node: &rlx_ir::Node,
899 graph: &Graph,
900 arena: &crate::arena::Arena,
901 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
902 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
903 rng: rlx_ir::RngOptions,
904) -> Thunk {
905 let Op::Compare(cmp) = &node.op else {
906 unreachable!()
907 };
908 {
909 let len = node.shape.num_elements().unwrap();
910 let lhs_n = graph.node(node.inputs[0]).shape.num_elements().unwrap();
911 let rhs_n = graph.node(node.inputs[1]).shape.num_elements().unwrap();
912 let lhs_scalar = lhs_n == 1 && len > 1;
913 let rhs_scalar = rhs_n == 1 && len > 1;
914 let in_dtype = graph.node(node.inputs[0]).shape.dtype();
915 let inputs_i64 = u8::from(in_dtype == rlx_ir::DType::I64);
916 let lhs_partial = lhs_n != len && lhs_n != 1;
924 let rhs_partial = rhs_n != len && rhs_n != 1;
925 let out_dims_s = get_static_dims(graph, node.id);
926 let lhs_dims_s = get_static_dims(graph, node.inputs[0]);
927 let rhs_dims_s = get_static_dims(graph, node.inputs[1]);
928 let (out_dims_bcast, bcast_lhs_strides, bcast_rhs_strides) = if (lhs_partial || rhs_partial)
929 && !out_dims_s.is_empty()
930 && !lhs_dims_s.is_empty()
931 && !rhs_dims_s.is_empty()
932 {
933 assert!(
937 lhs_dims_s.len() <= out_dims_s.len() && rhs_dims_s.len() <= out_dims_s.len(),
938 "Compare {:?} at {}: operand rank exceeds output rank \
939 (lhs {lhs_dims_s:?}, rhs {rhs_dims_s:?}, out {out_dims_s:?}) — \
940 broadcasting cannot reduce rank, so the graph's shape metadata is inconsistent",
941 cmp,
942 node.name.as_deref().unwrap_or("<unnamed>"),
943 );
944 (
945 out_dims_s.iter().map(|&d| d as u32).collect::<Vec<u32>>(),
946 broadcast_strides(&lhs_dims_s, &out_dims_s),
947 broadcast_strides(&rhs_dims_s, &out_dims_s),
948 )
949 } else {
950 (Vec::new(), Vec::new(), Vec::new())
951 };
952 Thunk::Compare {
953 lhs: node_offset(arena, node.inputs[0]),
954 rhs: node_offset(arena, node.inputs[1]),
955 dst: node_offset(arena, node.id),
956 len: len as u32,
957 op: *cmp,
958 inputs_i64,
959 inputs_elem_bytes: in_dtype.size_bytes() as u8,
960 dst_elem_bytes: node.shape.dtype().size_bytes() as u8,
961 lhs_scalar,
962 rhs_scalar,
963 lhs_len: lhs_n as u32,
964 rhs_len: rhs_n as u32,
965 out_dims_bcast,
966 bcast_lhs_strides,
967 bcast_rhs_strides,
968 }
969 }
970}
971
972#[allow(unused_variables)]
973pub(crate) fn compile_where(
974 node: &rlx_ir::Node,
975 graph: &Graph,
976 arena: &crate::arena::Arena,
977 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
978 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
979 rng: rlx_ir::RngOptions,
980) -> Thunk {
981 let Op::Where = &node.op else { unreachable!() };
982 {
983 let len = node.shape.num_elements().unwrap();
984 let cn = graph.node(node.inputs[0]).shape.num_elements().unwrap();
985 let tn = graph.node(node.inputs[1]).shape.num_elements().unwrap();
986 let fn_ = graph.node(node.inputs[2]).shape.num_elements().unwrap();
987 let cond_scalar = cn == 1 && len > 1;
988 let true_scalar = tn == 1 && len > 1;
989 let false_scalar = fn_ == 1 && len > 1;
990 let elem_bytes = node.shape.dtype().size_bytes() as u8;
991 let cond_elem_bytes = graph.node(node.inputs[0]).shape.dtype().size_bytes() as u8;
992 Thunk::Where {
993 cond: node_offset(arena, node.inputs[0]),
994 on_true: node_offset(arena, node.inputs[1]),
995 on_false: node_offset(arena, node.inputs[2]),
996 dst: node_offset(arena, node.id),
997 len: len as u32,
998 elem_bytes,
999 cond_elem_bytes,
1000 cond_scalar,
1001 true_scalar,
1002 false_scalar,
1003 }
1004 }
1005}
1006
1007#[allow(unused_variables)]
1008pub(crate) fn compile_fma(
1009 node: &rlx_ir::Node,
1010 graph: &Graph,
1011 arena: &crate::arena::Arena,
1012 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1013 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1014 rng: rlx_ir::RngOptions,
1015) -> Thunk {
1016 let Op::Fma = &node.op else { unreachable!() };
1017 {
1018 let len = node.shape.num_elements().unwrap();
1019 Thunk::Fma {
1020 a: node_offset(arena, node.inputs[0]),
1021 b: node_offset(arena, node.inputs[1]),
1022 c: node_offset(arena, node.inputs[2]),
1023 dst: node_offset(arena, node.id),
1024 len: len as u32,
1025 elem_bytes: node.shape.dtype().size_bytes() as u8,
1026 }
1027 }
1028}
1029
1030#[allow(unused_variables)]
1031pub(crate) fn compile_relu_backward(
1032 node: &rlx_ir::Node,
1033 graph: &Graph,
1034 arena: &crate::arena::Arena,
1035 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1036 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1037 rng: rlx_ir::RngOptions,
1038) -> Thunk {
1039 let Op::ReluBackward = &node.op else {
1040 unreachable!()
1041 };
1042 {
1043 let len: usize = (0..node.shape.rank())
1044 .map(|i| node.shape.dim(i).unwrap_static())
1045 .product();
1046 let x = node_offset(arena, node.inputs[0]);
1047 let dy = node_offset(arena, node.inputs[1]);
1048 let dx = node_offset(arena, node.id);
1049 match node.shape.dtype() {
1050 rlx_ir::DType::F64 => Thunk::ReluBackwardF64 {
1051 x,
1052 dy,
1053 dx,
1054 len: len as u32,
1055 },
1056 _ => Thunk::ReluBackward {
1057 x,
1058 dy,
1059 dx,
1060 len: len as u32,
1061 },
1062 }
1063 }
1064}
1065
1066#[allow(unused_variables)]
1067pub(crate) fn compile_activation_backward(
1068 node: &rlx_ir::Node,
1069 graph: &Graph,
1070 arena: &crate::arena::Arena,
1071 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1072 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1073 rng: rlx_ir::RngOptions,
1074) -> Thunk {
1075 let Op::ActivationBackward { kind } = &node.op else {
1076 unreachable!()
1077 };
1078 {
1079 let len: usize = (0..node.shape.rank())
1080 .map(|i| node.shape.dim(i).unwrap_static())
1081 .product();
1082 let x = node_offset(arena, node.inputs[0]);
1083 let dy = node_offset(arena, node.inputs[1]);
1084 let dx = node_offset(arena, node.id);
1085 match node.shape.dtype() {
1086 rlx_ir::DType::F64 => Thunk::ActivationBackwardF64 {
1087 x,
1088 dy,
1089 dx,
1090 len: len as u32,
1091 kind: *kind,
1092 },
1093 _ => Thunk::ActivationBackward {
1094 x,
1095 dy,
1096 dx,
1097 len: len as u32,
1098 kind: *kind,
1099 },
1100 }
1101 }
1102}
1103
1104#[allow(unused_variables)]
1105pub(crate) fn compile_gather_backward(
1106 node: &rlx_ir::Node,
1107 graph: &Graph,
1108 arena: &crate::arena::Arena,
1109 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1110 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1111 rng: rlx_ir::RngOptions,
1112) -> Thunk {
1113 let Op::GatherBackward { .. } = &node.op else {
1114 unreachable!()
1115 };
1116 {
1117 let dy_shape = &graph.node(node.inputs[0]).shape;
1118 let idx_shape = &graph.node(node.inputs[1]).shape;
1119 let out_shape = &node.shape;
1120 let rank = out_shape.rank();
1121 let axis = match &node.op {
1122 Op::GatherBackward { axis } => *axis,
1123 _ => 0,
1124 };
1125 let axis_u = if axis < 0 {
1126 (rank as i32 + axis) as usize
1127 } else {
1128 axis as usize
1129 };
1130 let outer: usize = (0..axis_u)
1131 .map(|i| dy_shape.dim(i).unwrap_static())
1132 .product::<usize>()
1133 .max(1);
1134 let num_idx = idx_shape.gather_index_count(axis_u);
1135 let trailing: usize = (axis_u + 1..dy_shape.rank())
1136 .map(|i| dy_shape.dim(i).unwrap_static())
1137 .product::<usize>()
1138 .max(1);
1139 let axis_dim = out_shape.dim(axis_u).unwrap_static();
1140 Thunk::GatherBackward {
1141 dy: node_offset(arena, node.inputs[0]),
1142 indices: node_offset(arena, node.inputs[1]),
1143 dst: node_offset(arena, node.id),
1144 outer: outer as u32,
1145 axis_dim: axis_dim as u32,
1146 num_idx: num_idx as u32,
1147 trailing: trailing as u32,
1148 }
1149 }
1150}
1151
1152#[allow(unused_variables)]
1153pub(crate) fn compile_concat(
1154 node: &rlx_ir::Node,
1155 graph: &Graph,
1156 arena: &crate::arena::Arena,
1157 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1158 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1159 rng: rlx_ir::RngOptions,
1160) -> Thunk {
1161 let Op::Concat { axis } = &node.op else {
1162 unreachable!()
1163 };
1164 {
1165 let out_shape = &node.shape;
1169 let rank = out_shape.rank();
1170 let outer: usize = (0..*axis)
1171 .map(|i| out_shape.dim(i).unwrap_static())
1172 .product::<usize>()
1173 .max(1);
1174 let inner: usize = (*axis + 1..rank)
1175 .map(|i| out_shape.dim(i).unwrap_static())
1176 .product::<usize>()
1177 .max(1);
1178 let total_axis = out_shape.dim(*axis).unwrap_static();
1179 let inputs: Vec<(usize, u32, u32)> = node
1180 .inputs
1181 .iter()
1182 .map(|&in_id| {
1183 let in_shape = &graph.node(in_id).shape;
1184 let in_axis = concat_axis_extent(in_shape, *axis, rank);
1185 let in_numel = in_shape.num_elements().unwrap_or(0) as u32;
1186 (node_offset(arena, in_id), in_axis as u32, in_numel)
1187 })
1188 .collect();
1189 let dst = node_offset(arena, node.id);
1190 match out_shape.dtype().size_bytes() {
1198 8 => Thunk::ConcatF64 {
1199 dst,
1200 outer: outer as u32,
1201 inner: inner as u32,
1202 total_axis: total_axis as u32,
1203 inputs,
1204 },
1205 _ => Thunk::Concat {
1206 dst,
1207 outer: outer as u32,
1208 inner: inner as u32,
1209 total_axis: total_axis as u32,
1210 inputs,
1211 },
1212 }
1213 }
1214}
1215
1216#[allow(unused_variables)]
1217pub(crate) fn compile_elementwise_region(
1218 node: &rlx_ir::Node,
1219 graph: &Graph,
1220 arena: &crate::arena::Arena,
1221 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1222 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1223 rng: rlx_ir::RngOptions,
1224) -> Thunk {
1225 let Op::ElementwiseRegion {
1226 chain,
1227 scalar_input_mask,
1228 input_modulus,
1229 prologue,
1230 ..
1231 } = &node.op
1232 else {
1233 unreachable!()
1234 };
1235 {
1236 if *prologue != rlx_ir::op::RegionPrologue::None {
1240 Thunk::Nop
1241 } else {
1242 let input_offs: Vec<usize> = node
1243 .inputs
1244 .iter()
1245 .map(|&id| node_offset(arena, id))
1246 .collect();
1247 let lane_of = |id: NodeId| {
1252 let n = graph.node(id).shape.num_elements().unwrap_or(0);
1253 let bytes = arena.byte_size(id);
1254 let elem = bytes.checked_div(n).unwrap_or(4);
1255 crate::thunk::types::LaneKind::resolve(graph.node(id).shape.dtype(), elem)
1256 };
1257 let in_lanes: Vec<crate::thunk::types::LaneKind> =
1258 node.inputs.iter().map(|&id| lane_of(id)).collect();
1259 Thunk::ElementwiseRegion {
1260 dst: node_offset(arena, node.id),
1261 len: node.shape.num_elements().unwrap_or(0) as u32,
1262 input_offs,
1263 chain: chain.clone(),
1264 scalar_input_mask: *scalar_input_mask,
1265 input_modulus: *input_modulus,
1266 in_lanes,
1267 out_lane: lane_of(node.id),
1268 }
1269 }
1270 }
1271}
1272
1273pub(crate) fn get_len(graph: &Graph, id: NodeId) -> usize {
1274 graph.node(id).shape.num_elements().unwrap_or(0)
1275}
1276
1277pub(crate) fn get_static_dims(graph: &Graph, id: NodeId) -> Vec<usize> {
1279 let dims = graph.node(id).shape.dims();
1280 let mut out = Vec::with_capacity(dims.len());
1281 for d in dims {
1282 if let Some(s) = match d {
1283 rlx_ir::Dim::Static(s) => Some(*s),
1284 _ => None,
1285 } {
1286 out.push(s);
1287 } else {
1288 return Vec::new();
1289 }
1290 }
1291 out
1292}
1293
1294pub(crate) fn concat_axis_extent(input: &rlx_ir::Shape, axis: usize, out_rank: usize) -> usize {
1297 let in_rank = input.rank();
1298 if axis >= out_rank {
1299 return 1;
1300 }
1301 if axis < in_rank {
1302 input.dim(axis).unwrap_static()
1303 } else {
1304 1
1305 }
1306}
1307
1308pub(crate) fn broadcast_src_index(src_idx: usize, in_len: usize) -> usize {
1309 if in_len == 0 { 0 } else { src_idx % in_len }
1310}
1311
1312macro_rules! concat_copy_rows {
1316 ($name:ident, $t:ty) => {
1317 pub(crate) fn $name(
1318 out: &mut [$t],
1319 inp: &[$t],
1320 outer: usize,
1321 copy_per_row: usize,
1322 row_stride: usize,
1323 dst_col_off: usize,
1324 in_numel: usize,
1325 ) {
1326 let need = outer.saturating_mul(copy_per_row.max(1));
1327 let broadcast_outer = in_numel < need;
1328 let out_ptr = out.as_mut_ptr() as usize;
1329 let inp_ptr = inp.as_ptr() as usize;
1330 let inp_len = inp.len();
1331 let run = |o0: usize, o1: usize| {
1332 let out = unsafe { std::slice::from_raw_parts_mut(out_ptr as *mut $t, out.len()) };
1333 let inp = unsafe { std::slice::from_raw_parts(inp_ptr as *const $t, inp_len) };
1334 for o in o0..o1 {
1335 let dst_row_start = o * row_stride + dst_col_off;
1336 if broadcast_outer {
1337 if in_numel == 1 {
1338 if copy_per_row == 1 {
1339 out[dst_row_start] = inp[0];
1340 } else {
1341 out[dst_row_start..dst_row_start + copy_per_row].fill(inp[0]);
1342 }
1343 } else if copy_per_row <= inp.len() {
1344 out[dst_row_start..dst_row_start + copy_per_row]
1345 .copy_from_slice(&inp[..copy_per_row]);
1346 } else if !inp.is_empty() {
1347 out[dst_row_start..dst_row_start + copy_per_row].fill(inp[0]);
1348 }
1349 } else {
1350 let src_row_start = o * copy_per_row;
1351 out[dst_row_start..dst_row_start + copy_per_row]
1352 .copy_from_slice(&inp[src_row_start..src_row_start + copy_per_row]);
1353 }
1354 }
1355 };
1356 if outer >= 8
1358 && copy_per_row >= 16
1359 && crate::pool::num_threads() > 1
1360 && crate::pool::should_parallelize(outer.saturating_mul(copy_per_row))
1361 {
1362 crate::pool::par_for(outer, 1, &|off, cnt| run(off, off + cnt));
1363 } else {
1364 run(0, outer);
1365 }
1366 }
1367 };
1368}
1369
1370concat_copy_rows!(concat_copy_rows_f32, f32);
1371concat_copy_rows!(concat_copy_rows_f64, f64);
1372
1373pub(crate) fn is_trailing_bias_broadcast(
1391 rhs_dims: &[rlx_ir::Dim],
1392 out_dims: &[rlx_ir::Dim],
1393) -> bool {
1394 if rhs_dims.len() > out_dims.len() {
1395 return false;
1396 }
1397 let off = out_dims.len() - rhs_dims.len();
1398 for i in 0..rhs_dims.len() {
1399 let r = match rhs_dims[i] {
1400 rlx_ir::Dim::Static(n) => n,
1401 _ => return false,
1402 };
1403 let o = match out_dims[off + i] {
1404 rlx_ir::Dim::Static(n) => n,
1405 _ => return false,
1406 };
1407 if r != o {
1408 return false;
1409 }
1410 }
1411 true
1412}
1413
1414pub(crate) fn broadcast_strides(in_dims: &[usize], out_dims: &[usize]) -> Vec<u32> {
1415 let r_out = out_dims.len();
1416 let r_in = in_dims.len();
1417 assert!(
1418 r_in <= r_out,
1419 "broadcast: input rank {r_in} > output rank {r_out}"
1420 );
1421 let pad = r_out - r_in;
1422 let mut strides = vec![0u32; r_out];
1423 let mut acc: usize = 1;
1424 for d in (0..r_out).rev() {
1425 let in_size = if d < pad { 1 } else { in_dims[d - pad] };
1426 if in_size == 1 {
1427 strides[d] = 0;
1428 } else {
1429 assert_eq!(
1430 in_size, out_dims[d],
1431 "broadcast: input dim {in_size} doesn't match output dim {} at axis {d}",
1432 out_dims[d]
1433 );
1434 strides[d] = acc as u32;
1435 acc *= in_size;
1436 }
1437 }
1438 strides
1439}
1440
1441#[inline(always)]
1442pub(crate) fn exec_elementwise_region(t: &Thunk, base: *mut u8) {
1443 let Thunk::ElementwiseRegion {
1444 dst,
1445 len,
1446 input_offs,
1447 chain,
1448 scalar_input_mask,
1449 input_modulus,
1450 in_lanes,
1451 out_lane,
1452 } = t
1453 else {
1454 unreachable!()
1455 };
1456 {
1457 let len = *len as usize;
1458 if !chain.is_empty() && len > 0 {
1459 let base_addr = base as usize;
1460 let dst = *dst;
1461 let scalar_mask = *scalar_input_mask;
1462 let out_lane = *out_lane;
1464 let eval = |gid: usize| {
1465 let v = region_eval_elem(
1466 gid,
1467 base_addr as *const u8,
1468 input_offs,
1469 in_lanes,
1470 chain,
1471 scalar_mask,
1472 input_modulus,
1473 );
1474 unsafe {
1477 out_lane.store(base_addr as *mut u8, dst, gid, v);
1478 }
1479 };
1480 if fast_conv_enabled() && crate::pool::should_parallelize(len) {
1481 crate::pool::par_for(len, crate::pool::chunk_floor(len), &|off, cnt| {
1482 for gid in off..off + cnt {
1483 eval(gid);
1484 }
1485 });
1486 } else {
1487 for gid in 0..len {
1488 eval(gid);
1489 }
1490 }
1491 }
1492 }
1493}
1494
1495#[inline(always)]
1496pub(crate) fn exec_transpose_f64(t: &Thunk, base: *mut u8) {
1497 let Thunk::TransposeF64 {
1498 src,
1499 dst,
1500 in_total,
1501 out_dims,
1502 in_strides,
1503 } = t
1504 else {
1505 unreachable!()
1506 };
1507 unsafe {
1508 let inp = sl_f64(*src, base, *in_total as usize);
1509 let out_total: usize = out_dims.iter().map(|d| *d as usize).product();
1510 let out = sl_mut_f64(*dst, base, out_total);
1511 transpose_walk_f64(inp, out, out_dims, in_strides);
1512 }
1513}
1514
1515#[inline(always)]
1516pub(crate) fn exec_activation_f64(t: &Thunk, base: *mut u8) {
1517 let Thunk::ActivationF64 {
1518 src,
1519 dst,
1520 len,
1521 kind,
1522 } = t
1523 else {
1524 unreachable!()
1525 };
1526 {
1527 let len = *len as usize;
1528 unsafe {
1529 let inp = sl_f64(*src, base, len);
1530 let out = sl_mut_f64(*dst, base, len);
1531 apply_activation_f64(inp, out, *kind);
1532 }
1533 }
1534}
1535
1536#[inline(always)]
1537pub(crate) fn exec_binary_full_f64(t: &Thunk, base: *mut u8) {
1538 let Thunk::BinaryFullF64 {
1539 lhs,
1540 rhs,
1541 dst,
1542 len,
1543 lhs_len,
1544 rhs_len,
1545 op,
1546 out_dims_bcast,
1547 bcast_lhs_strides,
1548 bcast_rhs_strides,
1549 } = t
1550 else {
1551 unreachable!()
1552 };
1553 {
1554 let len = *len as usize;
1555 let lhs_len = *lhs_len as usize;
1556 let rhs_len = *rhs_len as usize;
1557 unsafe {
1558 let l = sl_f64(*lhs, base, lhs_len);
1559 let r = sl_f64(*rhs, base, rhs_len);
1560 let d = sl_mut_f64(*dst, base, len);
1561 if lhs_len == len && rhs_len == len {
1562 for i in 0..len {
1563 d[i] = binary_op_f64(*op, l[i], r[i]);
1564 }
1565 } else if !out_dims_bcast.is_empty() {
1566 let rank = out_dims_bcast.len();
1570 let mut coords = vec![0u32; rank];
1571 for i in 0..len {
1572 let mut rem = i;
1573 for ax in (0..rank).rev() {
1574 let sz = out_dims_bcast[ax] as usize;
1575 coords[ax] = (rem % sz) as u32;
1576 rem /= sz;
1577 }
1578 let mut li: usize = 0;
1579 let mut ri: usize = 0;
1580 for ax in 0..rank {
1581 li += coords[ax] as usize * bcast_lhs_strides[ax] as usize;
1582 ri += coords[ax] as usize * bcast_rhs_strides[ax] as usize;
1583 }
1584 d[i] = binary_op_f64(*op, l[li], r[ri]);
1585 }
1586 } else {
1587 for i in 0..len {
1592 d[i] = binary_op_f64(*op, l[i % lhs_len], r[i % rhs_len]);
1593 }
1594 }
1595 }
1596 }
1597}
1598
1599#[inline(always)]
1600pub(crate) fn exec_binary_full_c64(t: &Thunk, base: *mut u8) {
1601 let Thunk::BinaryFullC64 {
1602 lhs,
1603 rhs,
1604 dst,
1605 len,
1606 lhs_len,
1607 rhs_len,
1608 op,
1609 out_dims_bcast,
1610 bcast_lhs_strides,
1611 bcast_rhs_strides,
1612 } = t
1613 else {
1614 unreachable!()
1615 };
1616 {
1617 let n_out = *len as usize;
1623 let n_l = *lhs_len as usize;
1624 let n_r = *rhs_len as usize;
1625 unsafe {
1626 let l = sl(*lhs, base, 2 * n_l);
1627 let r = sl(*rhs, base, 2 * n_r);
1628 let d = sl_mut(*dst, base, 2 * n_out);
1629 let do_c64 = |a_re: f32, a_im: f32, b_re: f32, b_im: f32| -> (f32, f32) {
1630 match op {
1631 BinaryOp::Add => (a_re + b_re, a_im + b_im),
1632 BinaryOp::Sub => (a_re - b_re, a_im - b_im),
1633 BinaryOp::Mul => (a_re * b_re - a_im * b_im, a_re * b_im + a_im * b_re),
1634 BinaryOp::Div => {
1635 let denom = b_re * b_re + b_im * b_im;
1636 (
1637 (a_re * b_re + a_im * b_im) / denom,
1638 (a_im * b_re - a_re * b_im) / denom,
1639 )
1640 }
1641 BinaryOp::Max
1642 | BinaryOp::Min
1643 | BinaryOp::Pow
1644 | BinaryOp::Mod
1645 | BinaryOp::BitAnd
1646 | BinaryOp::BitOr
1647 | BinaryOp::BitXor
1648 | BinaryOp::Shl
1649 | BinaryOp::Shr
1650 | BinaryOp::Atan2 => {
1651 unreachable!("C64 max/min/pow/mod/bitwise/atan2 rejected at lowering")
1652 }
1653 }
1654 };
1655 if n_l == n_out && n_r == n_out {
1656 for i in 0..n_out {
1657 let (re, im) = do_c64(l[2 * i], l[2 * i + 1], r[2 * i], r[2 * i + 1]);
1658 d[2 * i] = re;
1659 d[2 * i + 1] = im;
1660 }
1661 } else if !out_dims_bcast.is_empty() {
1662 let rank = out_dims_bcast.len();
1666 let mut coords = vec![0u32; rank];
1667 for i in 0..n_out {
1668 let mut rem = i;
1669 for ax in (0..rank).rev() {
1670 let sz = out_dims_bcast[ax] as usize;
1671 coords[ax] = (rem % sz) as u32;
1672 rem /= sz;
1673 }
1674 let mut li: usize = 0;
1675 let mut ri: usize = 0;
1676 for ax in 0..rank {
1677 li += coords[ax] as usize * bcast_lhs_strides[ax] as usize;
1678 ri += coords[ax] as usize * bcast_rhs_strides[ax] as usize;
1679 }
1680 let (re, im) = do_c64(l[2 * li], l[2 * li + 1], r[2 * ri], r[2 * ri + 1]);
1681 d[2 * i] = re;
1682 d[2 * i + 1] = im;
1683 }
1684 } else {
1685 for i in 0..n_out {
1687 let li = if n_l == 1 { 0 } else { i % n_l };
1688 let ri = if n_r == 1 { 0 } else { i % n_r };
1689 let (re, im) = do_c64(l[2 * li], l[2 * li + 1], r[2 * ri], r[2 * ri + 1]);
1690 d[2 * i] = re;
1691 d[2 * i + 1] = im;
1692 }
1693 }
1694 }
1695 }
1696}
1697
1698#[inline(always)]
1699pub(crate) fn exec_activation_c64(t: &Thunk, base: *mut u8) {
1700 let Thunk::ActivationC64 {
1701 src,
1702 dst,
1703 len,
1704 kind,
1705 } = t
1706 else {
1707 unreachable!()
1708 };
1709 {
1710 let n = *len as usize;
1711 unsafe {
1712 let s = sl(*src, base, 2 * n);
1713 let d = sl_mut(*dst, base, 2 * n);
1714 for i in 0..n {
1715 let a = s[2 * i];
1716 let b = s[2 * i + 1];
1717 let (re, im) = match kind {
1718 Activation::Neg => (-a, -b),
1719 Activation::Exp => {
1720 let ea = a.exp();
1722 (ea * b.cos(), ea * b.sin())
1723 }
1724 Activation::Log => {
1725 let r = (a * a + b * b).sqrt();
1727 (r.ln(), b.atan2(a))
1728 }
1729 Activation::Sqrt => {
1730 let r = (a * a + b * b).sqrt();
1733 let re = ((r + a) * 0.5).max(0.0).sqrt();
1734 let im_mag = ((r - a) * 0.5).max(0.0).sqrt();
1735 let im = if b >= 0.0 { im_mag } else { -im_mag };
1736 (re, im)
1737 }
1738 _ => unreachable!("non-C64 activation kind survived lowering"),
1739 };
1740 d[2 * i] = re;
1741 d[2 * i + 1] = im;
1742 }
1743 }
1744 }
1745}
1746
1747#[inline(always)]
1748pub(crate) fn exec_gather(t: &Thunk, base: *mut u8) {
1749 let Thunk::Gather {
1750 table,
1751 table_len,
1752 idx,
1753 dst,
1754 num_idx,
1755 trailing,
1756 idx_i64,
1757 table_bytes,
1758 } = t
1759 else {
1760 unreachable!()
1761 };
1762 {
1763 let (ni, tr) = (*num_idx as usize, *trailing as usize);
1764 let rows = *table_len as usize / tr.max(1);
1765 unsafe {
1766 if *table_bytes == 8 {
1767 let tab = sl_i64(*table, base, *table_len as usize);
1768 let out = sl_mut_i64(*dst, base, ni * tr);
1769 if *idx_i64 != 0 {
1770 let ids = sl_i64(*idx, base, ni);
1771 for i in 0..ni {
1772 let row = ids[i].max(0) as usize;
1773 if row < rows {
1774 out[i * tr..(i + 1) * tr]
1775 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1776 }
1777 }
1778 } else {
1779 let ids = sl(*idx, base, ni);
1780 for i in 0..ni {
1781 let row = ids[i] as usize;
1782 if row < rows {
1783 out[i * tr..(i + 1) * tr]
1784 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1785 }
1786 }
1787 }
1788 } else {
1789 let tab = sl(*table, base, *table_len as usize);
1790 let out = sl_mut(*dst, base, ni * tr);
1791 if *idx_i64 != 0 {
1792 let ids = sl_i64(*idx, base, ni);
1793 for i in 0..ni {
1794 let row = ids[i].max(0) as usize;
1795 if row < rows {
1796 out[i * tr..(i + 1) * tr]
1797 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1798 }
1799 }
1800 } else {
1801 let ids = sl(*idx, base, ni);
1802 for i in 0..ni {
1803 let row = ids[i] as usize;
1804 if row < rows {
1805 out[i * tr..(i + 1) * tr]
1806 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1807 }
1808 }
1809 }
1810 }
1811 }
1812 }
1813}
1814
1815#[inline(always)]
1816pub(crate) fn exec_activation_in_place(t: &Thunk, base: *mut u8) {
1817 let Thunk::ActivationInPlace { data, len, act } = t else {
1818 unreachable!()
1819 };
1820 {
1821 let len = *len as usize;
1822 unsafe {
1823 let d = sl_mut(*data, base, len);
1824 let par = crate::pool::should_parallelize(len);
1830 macro_rules! apply {
1831 ($f:expr) => {{
1832 let f = $f;
1833 if par {
1834 use rayon::prelude::*;
1835 d.par_iter_mut().for_each(|v| *v = f(*v));
1836 } else {
1837 for v in d.iter_mut() {
1838 *v = f(*v);
1839 }
1840 }
1841 }};
1842 }
1843 match act {
1844 Activation::Gelu => crate::kernels::par_gelu_inplace(d),
1845 Activation::GeluApprox => crate::kernels::par_gelu_approx_inplace(d),
1846 Activation::Silu => crate::kernels::par_silu_inplace(d),
1847 Activation::Relu => apply!(|x: f32| x.max(0.0)),
1848 Activation::Sigmoid => apply!(|x: f32| 1.0 / (1.0 + (-x).exp())),
1849 Activation::Tanh => crate::vmath::vvtanhf_hot_inplace(d),
1850 Activation::Exp => crate::vmath::vvexpf_hot_inplace(d),
1851 Activation::Log => crate::vmath::vvlogf_inplace(d),
1852 Activation::Sqrt => crate::vmath::vvsqrtf_inplace(d),
1853 Activation::Rsqrt => crate::vmath::vvrsqrtf_inplace(d),
1854 Activation::Neg => apply!(|x: f32| -x),
1855 Activation::Abs => apply!(|x: f32| x.abs()),
1856 Activation::Round => apply!(|x: f32| x.round()),
1857 Activation::Sin => apply!(|x: f32| x.sin()),
1858 Activation::Cos => apply!(|x: f32| x.cos()),
1859 Activation::Tan => apply!(|x: f32| x.tan()),
1860 Activation::Atan => apply!(|x: f32| x.atan()),
1861 Activation::Recip => crate::vmath::vvrecf_inplace(d),
1862 Activation::Floor => apply!(|x: f32| x.floor()),
1863 Activation::Ceil => apply!(|x: f32| x.ceil()),
1864 Activation::Sign => {
1865 apply!(|x: f32| if x > 0.0 {
1866 1.0
1867 } else if x < 0.0 {
1868 -1.0
1869 } else {
1870 0.0
1871 })
1872 }
1873 Activation::Softplus => apply!(|x: f32| x.max(0.0) + (-(x.abs())).exp().ln_1p()),
1874 Activation::Elu => apply!(|x: f32| if x > 0.0 { x } else { x.exp() - 1.0 }),
1875 Activation::Erf => apply!(|x: f32| erf_f32(x)),
1876 Activation::HardSwish => apply!(|x: f32| x * (x + 3.0).clamp(0.0, 6.0) / 6.0),
1877 Activation::HardSigmoid => apply!(|x: f32| (x / 6.0 + 0.5).clamp(0.0, 1.0)),
1878 Activation::Mish => {
1879 apply!(|x: f32| x * (x.max(0.0) + (-(x.abs())).exp().ln_1p()).tanh())
1880 }
1881 Activation::Softsign => apply!(|x: f32| x / (1.0 + x.abs())),
1882 Activation::LogSigmoid => {
1883 apply!(|x: f32| x.min(0.0) - (-(x.abs())).exp().ln_1p())
1884 }
1885 }
1886 }
1887 }
1888}
1889
1890#[inline(always)]
1891pub(crate) fn exec_kv_append(t: &Thunk, base: *mut u8) {
1897 let Thunk::KvAppend {
1898 src,
1899 dst,
1900 outer,
1901 seq_cap,
1902 pos,
1903 inner_bytes,
1904 } = t
1905 else {
1906 unreachable!()
1907 };
1908 let (outer, seq_cap, pos, inner_bytes) = (
1909 *outer as usize,
1910 *seq_cap as usize,
1911 *pos as usize,
1912 *inner_bytes as usize,
1913 );
1914 if inner_bytes == 0 || outer == 0 || pos >= seq_cap {
1915 return;
1919 }
1920 unsafe {
1921 let row = base.add(*src);
1922 let cache = base.add(*dst);
1923 if outer == 1 {
1924 std::ptr::copy_nonoverlapping(row, cache.add(pos * inner_bytes), inner_bytes);
1926 } else {
1927 let stride = seq_cap * inner_bytes;
1928 for o in 0..outer {
1929 std::ptr::copy_nonoverlapping(
1930 row.add(o * inner_bytes),
1931 cache.add(o * stride + pos * inner_bytes),
1932 inner_bytes,
1933 );
1934 }
1935 }
1936 }
1937}
1938
1939pub(crate) fn exec_concat(t: &Thunk, base: *mut u8) {
1940 let Thunk::Concat {
1941 dst,
1942 outer,
1943 inner,
1944 total_axis,
1945 inputs,
1946 } = t
1947 else {
1948 unreachable!()
1949 };
1950 {
1951 let outer = *outer as usize;
1952 let inner = *inner as usize;
1953 let total_axis = *total_axis as usize;
1954 let row_stride = total_axis * inner;
1955 let out_total = outer * row_stride;
1956 unsafe {
1957 let out = sl_mut(*dst, base, out_total);
1958 let mut cum: usize = 0;
1959 for (src_off, in_axis, in_numel) in inputs {
1960 let in_axis = *in_axis as usize;
1961 let copy_per_row = in_axis * inner;
1962 let dst_col_off = cum * inner;
1963 let inp = sl(*src_off, base, (*in_numel as usize).max(1));
1964 concat_copy_rows_f32(
1965 out,
1966 inp,
1967 outer,
1968 copy_per_row,
1969 row_stride,
1970 dst_col_off,
1971 *in_numel as usize,
1972 );
1973 cum += in_axis;
1974 }
1975 }
1976 }
1977}
1978
1979#[inline(always)]
1980pub(crate) fn exec_concat_f64(t: &Thunk, base: *mut u8) {
1981 let Thunk::ConcatF64 {
1982 dst,
1983 outer,
1984 inner,
1985 total_axis,
1986 inputs,
1987 } = t
1988 else {
1989 unreachable!()
1990 };
1991 {
1992 let outer = *outer as usize;
1993 let inner = *inner as usize;
1994 let total_axis = *total_axis as usize;
1995 let row_stride = total_axis * inner;
1996 let out_total = outer * row_stride;
1997 unsafe {
1998 let out = sl_mut_f64(*dst, base, out_total);
1999 let mut cum: usize = 0;
2000 for (src_off, in_axis, in_numel) in inputs {
2001 let in_axis = *in_axis as usize;
2002 let copy_per_row = in_axis * inner;
2003 let dst_col_off = cum * inner;
2004 let inp = sl_f64(*src_off, base, (*in_numel as usize).max(1));
2005 concat_copy_rows_f64(
2006 out,
2007 inp,
2008 outer,
2009 copy_per_row,
2010 row_stride,
2011 dst_col_off,
2012 *in_numel as usize,
2013 );
2014 cum += in_axis;
2015 }
2016 }
2017 }
2018}
2019
2020#[inline(always)]
2021pub(crate) fn exec_scatter_add(t: &Thunk, base: *mut u8) {
2022 let Thunk::ScatterAdd {
2023 updates,
2024 indices,
2025 dst,
2026 num_updates,
2027 out_dim,
2028 trailing,
2029 } = t
2030 else {
2031 unreachable!()
2032 };
2033 {
2034 let num_updates = *num_updates as usize;
2035 let out_dim = *out_dim as usize;
2036 let trailing = *trailing as usize;
2037 unsafe {
2038 let upd = sl(*updates, base, num_updates * trailing);
2039 let ids = sl(*indices, base, num_updates);
2040 let out = sl_mut(*dst, base, out_dim * trailing);
2041 for v in out.iter_mut() {
2043 *v = 0.0;
2044 }
2045 for i in 0..num_updates {
2046 let row = ids[i] as usize;
2047 debug_assert!(row < out_dim, "ScatterAdd index out of range");
2048 let src_off = i * trailing;
2049 let dst_off = row * trailing;
2050 for j in 0..trailing {
2051 out[dst_off + j] += upd[src_off + j];
2052 }
2053 }
2054 }
2055 }
2056}
2057
2058#[inline(always)]
2059pub(crate) fn exec_scatter_nd(t: &Thunk, base: *mut u8) {
2060 let Thunk::ScatterNd {
2061 data,
2062 indices,
2063 updates,
2064 dst,
2065 data_shape,
2066 indices_shape,
2067 data_len,
2068 updates_len,
2069 indices_len,
2070 indices_i64,
2071 reduction,
2072 } = t
2073 else {
2074 unreachable!()
2075 };
2076 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2077 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2078 let data_len = *data_len as usize;
2079 let updates_len = *updates_len as usize;
2080 let indices_len = *indices_len as usize;
2081 unsafe {
2082 let data_s = sl(*data, base, data_len);
2083 let updates_s = sl(*updates, base, updates_len);
2084 let out = sl_mut(*dst, base, data_len);
2085 let idx_i64: Vec<i64> = if *indices_i64 != 0 {
2086 let ptr = base.add(*indices) as *const i64;
2087 std::slice::from_raw_parts(ptr, indices_len).to_vec()
2088 } else {
2089 sl(*indices, base, indices_len)
2090 .iter()
2091 .map(|&x| x as i64)
2092 .collect()
2093 };
2094 let (offsets, slice) =
2095 crate::onnx_indexing::scatter_nd_dst_offsets(&data_shape, &idx_i64, &indices_shape);
2096 crate::onnx_indexing::scatter_nd_into_f32(
2097 data_s, updates_s, out, &offsets, slice, *reduction,
2098 );
2099 }
2100}
2101
2102fn load_indices_i64(base: *mut u8, off: usize, len: usize, as_i64: u8) -> Vec<i64> {
2103 unsafe {
2104 if as_i64 != 0 {
2105 let ptr = base.add(off) as *const i64;
2106 std::slice::from_raw_parts(ptr, len).to_vec()
2107 } else {
2108 sl(off, base, len).iter().map(|&x| x as i64).collect()
2109 }
2110 }
2111}
2112
2113#[inline(always)]
2114pub(crate) fn exec_scatter_elements(t: &Thunk, base: *mut u8) {
2115 let Thunk::ScatterElements {
2116 data,
2117 indices,
2118 updates,
2119 dst,
2120 data_shape,
2121 indices_shape,
2122 data_len,
2123 updates_len,
2124 indices_len,
2125 indices_i64,
2126 axis,
2127 reduction,
2128 } = t
2129 else {
2130 unreachable!()
2131 };
2132 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2133 let idx_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2134 unsafe {
2135 let data_s = sl(*data, base, *data_len as usize);
2136 let updates_s = sl(*updates, base, *updates_len as usize);
2137 let out = sl_mut(*dst, base, *data_len as usize);
2138 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
2139 crate::onnx_indexing::scatter_elements_f32(
2140 data_s,
2141 updates_s,
2142 &idx,
2143 out,
2144 &data_shape,
2145 &idx_shape,
2146 *axis,
2147 *reduction,
2148 );
2149 }
2150}
2151
2152#[inline(always)]
2153pub(crate) fn exec_gather_nd(t: &Thunk, base: *mut u8) {
2154 let Thunk::GatherNd {
2155 data,
2156 indices,
2157 dst,
2158 data_shape,
2159 indices_shape,
2160 data_len,
2161 indices_len,
2162 out_len,
2163 indices_i64,
2164 batch_dims,
2165 } = t
2166 else {
2167 unreachable!()
2168 };
2169 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2170 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2171 unsafe {
2172 let data_s = sl(*data, base, *data_len as usize);
2173 let out = sl_mut(*dst, base, *out_len as usize);
2174 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
2175 let (offsets, slice) = crate::onnx_indexing::gather_nd_src_offsets(
2176 &data_shape,
2177 &idx,
2178 &indices_shape,
2179 (*batch_dims).max(0) as usize,
2180 );
2181 for (t, &off) in offsets.iter().enumerate() {
2182 let d0 = t * slice;
2183 for j in 0..slice {
2184 if let (Some(&s), Some(d)) = (data_s.get(off + j), out.get_mut(d0 + j)) {
2185 *d = s;
2186 }
2187 }
2188 }
2189 }
2190}
2191
2192#[inline(always)]
2193pub(crate) fn exec_gather_elements(t: &Thunk, base: *mut u8) {
2194 let Thunk::GatherElements {
2195 data,
2196 indices,
2197 dst,
2198 data_shape,
2199 indices_shape,
2200 data_len,
2201 indices_len,
2202 out_len,
2203 indices_i64,
2204 data_elem_bytes,
2205 axis,
2206 } = t
2207 else {
2208 unreachable!()
2209 };
2210 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2211 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2212 let elem_bytes = (*data_elem_bytes as usize).max(1);
2213 unsafe {
2214 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
2215 if elem_bytes == 4 {
2216 let data_s = sl(*data, base, *data_len as usize);
2218 let out = sl_mut(*dst, base, *out_len as usize);
2219 crate::onnx_indexing::gather_elements_f32(
2220 data_s,
2221 &idx,
2222 out,
2223 &data_shape,
2224 &indices_shape,
2225 *axis,
2226 );
2227 } else {
2228 let data_b =
2230 std::slice::from_raw_parts(base.add(*data), *data_len as usize * elem_bytes);
2231 let out_b =
2232 std::slice::from_raw_parts_mut(base.add(*dst), *out_len as usize * elem_bytes);
2233 crate::onnx_indexing::gather_elements_bytes(
2234 data_b,
2235 &idx,
2236 out_b,
2237 &data_shape,
2238 &indices_shape,
2239 *axis,
2240 elem_bytes,
2241 );
2242 }
2243 }
2244}
2245
2246#[inline(always)]
2247pub(crate) fn exec_relu_backward(t: &Thunk, base: *mut u8) {
2248 let Thunk::ReluBackward { x, dy, dx, len } = t else {
2249 unreachable!()
2250 };
2251 {
2252 let len = *len as usize;
2253 unsafe {
2254 let xs = sl(*x, base, len);
2255 let dys = sl(*dy, base, len);
2256 let out = sl_mut(*dx, base, len);
2257 if fast_conv_enabled() && crate::pool::should_parallelize(len) {
2258 let oa = out.as_mut_ptr() as usize;
2259 crate::pool::par_for(len, crate::pool::chunk_floor(len), &|off, cnt| {
2260 for i in off..off + cnt {
2261 *((oa as *mut f32).add(i)) = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2262 }
2263 });
2264 } else {
2265 for i in 0..len {
2266 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2267 }
2268 }
2269 }
2270 }
2271}
2272
2273#[inline(always)]
2274pub(crate) fn exec_relu_backward_f64(t: &Thunk, base: *mut u8) {
2275 let Thunk::ReluBackwardF64 { x, dy, dx, len } = t else {
2276 unreachable!()
2277 };
2278 {
2279 let len = *len as usize;
2280 unsafe {
2281 let xs = sl_f64(*x, base, len);
2282 let dys = sl_f64(*dy, base, len);
2283 let out = sl_mut_f64(*dx, base, len);
2284 for i in 0..len {
2285 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2286 }
2287 }
2288 }
2289}
2290
2291#[inline(always)]
2292pub(crate) fn exec_activation_backward(t: &Thunk, base: *mut u8) {
2293 let Thunk::ActivationBackward {
2294 x,
2295 dy,
2296 dx,
2297 len,
2298 kind,
2299 } = t
2300 else {
2301 unreachable!()
2302 };
2303 {
2304 let len = *len as usize;
2305 unsafe {
2306 let xs = sl(*x, base, len);
2307 let dys = sl(*dy, base, len);
2308 let out = sl_mut(*dx, base, len);
2309 activation_backward_kernel(*kind, xs, dys, out);
2310 }
2311 }
2312}
2313
2314#[inline(always)]
2315pub(crate) fn exec_activation_backward_f64(t: &Thunk, base: *mut u8) {
2316 let Thunk::ActivationBackwardF64 {
2317 x,
2318 dy,
2319 dx,
2320 len,
2321 kind,
2322 } = t
2323 else {
2324 unreachable!()
2325 };
2326 {
2327 let len = *len as usize;
2328 unsafe {
2329 let xs = sl_f64(*x, base, len);
2330 let dys = sl_f64(*dy, base, len);
2331 let out = sl_mut_f64(*dx, base, len);
2332 activation_backward_kernel_f64(*kind, xs, dys, out);
2333 }
2334 }
2335}
2336
2337#[inline(always)]
2338pub(crate) fn exec_gather_backward(t: &Thunk, base: *mut u8) {
2339 let Thunk::GatherBackward {
2340 dy,
2341 indices,
2342 dst,
2343 outer,
2344 axis_dim,
2345 num_idx,
2346 trailing,
2347 } = t
2348 else {
2349 unreachable!()
2350 };
2351 {
2352 let (outer, axis_dim, num_idx, trailing) = (
2353 *outer as usize,
2354 *axis_dim as usize,
2355 *num_idx as usize,
2356 *trailing as usize,
2357 );
2358 unsafe {
2359 let dys = sl(*dy, base, outer * num_idx * trailing);
2360 let ids = sl(*indices, base, num_idx);
2361 let out = sl_mut(*dst, base, outer * axis_dim * trailing);
2362 for v in out.iter_mut() {
2363 *v = 0.0;
2364 }
2365 crate::training_bwd::gather_axis_backward(
2366 dys, ids, out, outer, axis_dim, num_idx, trailing,
2367 );
2368 }
2369 }
2370}
2371
2372#[inline(always)]
2373pub(crate) fn exec_gather_axis(t: &Thunk, base: *mut u8) {
2374 let Thunk::GatherAxis {
2375 table,
2376 idx,
2377 dst,
2378 outer,
2379 axis_dim,
2380 num_idx,
2381 trailing,
2382 idx_i64,
2383 table_bytes,
2384 } = t
2385 else {
2386 unreachable!()
2387 };
2388 {
2389 let outer = *outer as usize;
2390 let axis_dim = *axis_dim as usize;
2391 let num_idx = *num_idx as usize;
2392 let trailing = *trailing as usize;
2393 unsafe {
2394 if *table_bytes == 8 {
2395 let tab = sl_i64(*table, base, outer * axis_dim * trailing);
2396 let out = sl_mut_i64(*dst, base, outer * num_idx * trailing);
2397 for o in 0..outer {
2398 let tab_outer = o * axis_dim * trailing;
2399 let out_outer = o * num_idx * trailing;
2400 if *idx_i64 != 0 {
2401 let ids = sl_i64(*idx, base, num_idx);
2402 for k in 0..num_idx {
2403 let row = ids[k].max(0) as usize;
2404 if row < axis_dim {
2405 let tab_row = tab_outer + row * trailing;
2406 let out_row = out_outer + k * trailing;
2407 out[out_row..out_row + trailing]
2408 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2409 }
2410 }
2411 } else {
2412 let ids = sl(*idx, base, num_idx);
2413 for k in 0..num_idx {
2414 let row = ids[k] as usize;
2415 if row < axis_dim {
2416 let tab_row = tab_outer + row * trailing;
2417 let out_row = out_outer + k * trailing;
2418 out[out_row..out_row + trailing]
2419 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2420 }
2421 }
2422 }
2423 }
2424 } else {
2425 let tab = sl(*table, base, outer * axis_dim * trailing);
2426 let out = sl_mut(*dst, base, outer * num_idx * trailing);
2427 for o in 0..outer {
2428 let tab_outer = o * axis_dim * trailing;
2429 let out_outer = o * num_idx * trailing;
2430 if *idx_i64 != 0 {
2431 let ids = sl_i64(*idx, base, num_idx);
2432 for k in 0..num_idx {
2433 let row = ids[k].max(0) as usize;
2434 if row < axis_dim {
2435 let tab_row = tab_outer + row * trailing;
2436 let out_row = out_outer + k * trailing;
2437 out[out_row..out_row + trailing]
2438 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2439 }
2440 }
2441 } else {
2442 let ids = sl(*idx, base, num_idx);
2443 for k in 0..num_idx {
2444 let row = ids[k] as usize;
2445 if row < axis_dim {
2446 let tab_row = tab_outer + row * trailing;
2447 let out_row = out_outer + k * trailing;
2448 out[out_row..out_row + trailing]
2449 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2450 }
2451 }
2452 }
2453 }
2454 }
2455 }
2456 }
2457}
2458
2459#[inline(always)]
2460pub(crate) fn exec_reverse(t: &Thunk, base: *mut u8) {
2461 let Thunk::Reverse {
2462 src,
2463 dst,
2464 dims,
2465 rev_mask,
2466 elem_bytes,
2467 } = t
2468 else {
2469 unreachable!()
2470 };
2471 {
2472 let eb = *elem_bytes as usize;
2473 let rank = dims.len();
2474 let total: usize = dims.iter().map(|&d| d as usize).product::<usize>().max(1);
2475 let mut strides = vec![1usize; rank];
2476 for i in (0..rank.saturating_sub(1)).rev() {
2477 strides[i] = strides[i + 1] * dims[i + 1] as usize;
2478 }
2479 unsafe {
2480 let src_base = base.add(*src);
2481 let dst_base = base.add(*dst);
2482 for o in 0..total {
2483 let mut rem = o;
2484 let mut in_flat = 0usize;
2485 for ax in 0..rank {
2486 let idx = rem / strides[ax];
2487 rem %= strides[ax];
2488 let in_idx = if rev_mask[ax] {
2489 dims[ax] as usize - 1 - idx
2490 } else {
2491 idx
2492 };
2493 in_flat += in_idx * strides[ax];
2494 }
2495 std::ptr::copy_nonoverlapping(src_base.add(in_flat * eb), dst_base.add(o * eb), eb);
2496 }
2497 }
2498 }
2499}
2500
2501pub unsafe fn execute_reverse(
2507 src: usize,
2508 dst: usize,
2509 dims: &[u32],
2510 rev_mask: &[bool],
2511 elem_bytes: usize,
2512 base: *mut u8,
2513) {
2514 let rank = dims.len();
2515 let total: usize = dims.iter().map(|&d| d as usize).product::<usize>().max(1);
2516 let mut strides = vec![1usize; rank];
2517 for i in (0..rank.saturating_sub(1)).rev() {
2518 strides[i] = strides[i + 1] * dims[i + 1] as usize;
2519 }
2520 unsafe {
2521 let src_base = base.add(src);
2522 let dst_base = base.add(dst);
2523 for o in 0..total {
2524 let mut rem = o;
2525 let mut in_flat = 0usize;
2526 for ax in 0..rank {
2527 let idx = rem / strides[ax];
2528 rem %= strides[ax];
2529 let in_idx = if rev_mask[ax] {
2530 dims[ax] as usize - 1 - idx
2531 } else {
2532 idx
2533 };
2534 in_flat += in_idx * strides[ax];
2535 }
2536 std::ptr::copy_nonoverlapping(
2537 src_base.add(in_flat * elem_bytes),
2538 dst_base.add(o * elem_bytes),
2539 elem_bytes,
2540 );
2541 }
2542 }
2543}
2544
2545#[allow(clippy::too_many_arguments)]
2553pub unsafe fn execute_pad(
2554 src: usize,
2555 dst: usize,
2556 in_dims: &[u32],
2557 before: &[u32],
2558 after: &[u32],
2559 mode: rlx_ir::PadMode,
2560 fill: &[u8],
2561 elem_bytes: usize,
2562 base: *mut u8,
2563) {
2564 use rlx_ir::PadMode;
2565 let rank = in_dims.len();
2566 let in_dims: Vec<usize> = in_dims.iter().map(|&d| d as usize).collect();
2567 let out_dims: Vec<usize> = (0..rank)
2568 .map(|i| in_dims[i] + before[i] as usize + after[i] as usize)
2569 .collect();
2570 let total: usize = out_dims.iter().product::<usize>().max(1);
2571 let mut in_strides = vec![1usize; rank];
2572 let mut out_strides = vec![1usize; rank];
2573 for i in (0..rank.saturating_sub(1)).rev() {
2574 in_strides[i] = in_strides[i + 1] * in_dims[i + 1];
2575 out_strides[i] = out_strides[i + 1] * out_dims[i + 1];
2576 }
2577 unsafe {
2578 let src_base = base.add(src);
2579 let dst_base = base.add(dst);
2580 for o in 0..total {
2581 let mut rem = o;
2582 let mut in_flat = 0usize;
2583 let mut is_fill = false;
2584 for ax in 0..rank {
2585 let oc = (rem / out_strides[ax]) as isize;
2586 rem %= out_strides[ax];
2587 if before[ax] == 0 && after[ax] == 0 {
2589 in_flat += oc as usize * in_strides[ax];
2590 continue;
2591 }
2592 let n = in_dims[ax] as isize;
2593 let p = oc - before[ax] as isize;
2594 let ic: isize = match mode {
2595 PadMode::Constant(_) => {
2596 if p < 0 || p >= n {
2597 is_fill = true;
2598 break;
2599 }
2600 p
2601 }
2602 PadMode::Replicate => p.clamp(0, n - 1),
2603 PadMode::Circular => ((p % n) + n) % n,
2604 PadMode::Reflect => {
2605 let period = 2 * (n - 1);
2606 let mut i = ((p % period) + period) % period;
2607 if i >= n {
2608 i = period - i;
2609 }
2610 i
2611 }
2612 };
2613 in_flat += ic as usize * in_strides[ax];
2614 }
2615 let dptr = dst_base.add(o * elem_bytes);
2616 if is_fill {
2617 std::ptr::copy_nonoverlapping(fill.as_ptr(), dptr, elem_bytes);
2618 } else {
2619 std::ptr::copy_nonoverlapping(src_base.add(in_flat * elem_bytes), dptr, elem_bytes);
2620 }
2621 }
2622 }
2623}
2624
2625#[allow(clippy::too_many_arguments)]
2629pub unsafe fn execute_slice(
2630 src: usize,
2631 dst: usize,
2632 in_dims: &[u32],
2633 axis: usize,
2634 start: usize,
2635 len: usize,
2636 step: i64,
2637 elem_bytes: usize,
2638 base: *mut u8,
2639) {
2640 let rank = in_dims.len();
2641 let in_dims: Vec<usize> = in_dims.iter().map(|&d| d as usize).collect();
2642 let mut out_dims = in_dims.clone();
2643 out_dims[axis] = len;
2644 let total: usize = out_dims.iter().product::<usize>().max(1);
2645 let mut in_strides = vec![1usize; rank];
2646 let mut out_strides = vec![1usize; rank];
2647 for i in (0..rank.saturating_sub(1)).rev() {
2648 in_strides[i] = in_strides[i + 1] * in_dims[i + 1];
2649 out_strides[i] = out_strides[i + 1] * out_dims[i + 1];
2650 }
2651 unsafe {
2652 let src_base = base.add(src);
2653 let dst_base = base.add(dst);
2654 for o in 0..total {
2655 let mut rem = o;
2656 let mut in_flat = 0usize;
2657 for ax in 0..rank {
2658 let oc = rem / out_strides[ax];
2659 rem %= out_strides[ax];
2660 let ic = if ax == axis {
2661 (start as i64 + oc as i64 * step) as usize
2662 } else {
2663 oc
2664 };
2665 in_flat += ic * in_strides[ax];
2666 }
2667 std::ptr::copy_nonoverlapping(
2668 src_base.add(in_flat * elem_bytes),
2669 dst_base.add(o * elem_bytes),
2670 elem_bytes,
2671 );
2672 }
2673 }
2674}
2675
2676pub unsafe fn execute_gather_backward_f32(
2677 dy: usize,
2678 indices: usize,
2679 dst: usize,
2680 outer: u32,
2681 axis_dim: u32,
2682 num_idx: u32,
2683 trailing: u32,
2684 base: *mut u8,
2685) {
2686 let (outer, axis_dim, num_idx, trailing) = (
2687 outer as usize,
2688 axis_dim as usize,
2689 num_idx as usize,
2690 trailing as usize,
2691 );
2692 let out = sl_mut(dst, base, outer * axis_dim * trailing);
2693 out.fill(0.0);
2694 crate::training_bwd::gather_axis_backward(
2695 sl(dy, base, outer * num_idx * trailing),
2696 sl(indices, base, num_idx),
2697 out,
2698 outer,
2699 axis_dim,
2700 num_idx,
2701 trailing,
2702 );
2703}
2704
2705#[inline]
2708pub(crate) fn region_activation_scalar(act: rlx_ir::op::Activation, x: f32) -> f32 {
2709 use rlx_ir::op::Activation as A;
2710 const GC: f32 = 0.797_884_6; match act {
2712 A::Relu => x.max(0.0),
2713 A::Gelu | A::GeluApprox => 0.5 * x * (1.0 + (GC * (x + 0.044715 * x * x * x)).tanh()),
2714 A::Silu => x / (1.0 + (-x).exp()),
2715 A::Sigmoid => 1.0 / (1.0 + (-x).exp()),
2716 A::Tanh => x.tanh(),
2717 A::Exp => x.exp(),
2718 A::Log => x.ln(),
2719 A::Sqrt => x.sqrt(),
2720 A::Rsqrt => 1.0 / x.sqrt(),
2721 A::Neg => -x,
2722 A::Abs => x.abs(),
2723 A::Sin => x.sin(),
2724 A::Cos => x.cos(),
2725 A::Tan => x.tan(),
2726 A::Atan => x.atan(),
2727 A::Recip => 1.0 / x,
2728 A::Round => x.round(),
2729 A::Floor => x.floor(),
2730 A::Ceil => x.ceil(),
2731 A::Sign => {
2732 if x > 0.0 {
2733 1.0
2734 } else if x < 0.0 {
2735 -1.0
2736 } else {
2737 0.0
2738 }
2739 }
2740 A::Softplus => x.max(0.0) + (-(x.abs())).exp().ln_1p(),
2741 A::Elu => {
2742 if x > 0.0 {
2743 x
2744 } else {
2745 x.exp() - 1.0
2746 }
2747 }
2748 A::Erf => erf_f32(x),
2749 A::HardSwish => x * (x + 3.0).clamp(0.0, 6.0) / 6.0,
2750 A::HardSigmoid => (x / 6.0 + 0.5).clamp(0.0, 1.0),
2751 A::Mish => x * (x.max(0.0) + (-(x.abs())).exp().ln_1p()).tanh(),
2752 A::Softsign => x / (1.0 + x.abs()),
2753 A::LogSigmoid => x.min(0.0) - (-(x.abs())).exp().ln_1p(),
2754 }
2755}
2756
2757#[inline]
2758pub(crate) fn region_binary_scalar(op: rlx_ir::op::BinaryOp, l: f32, r: f32) -> f32 {
2759 use rlx_ir::op::BinaryOp as B;
2760 match op {
2761 B::Add => l + r,
2762 B::Sub => l - r,
2763 B::Mul => l * r,
2764 B::Div => l / r,
2765 B::Max => l.max(r),
2766 B::Min => l.min(r),
2767 B::Pow => l.powf(r),
2768 B::Mod => l % r,
2769 B::Atan2 => l.atan2(r),
2770 B::BitAnd => ((l as i64) & (r as i64)) as f32,
2771 B::BitOr => ((l as i64) | (r as i64)) as f32,
2772 B::BitXor => ((l as i64) ^ (r as i64)) as f32,
2773 B::Shl => (l as i64).wrapping_shl(r as u32) as f32,
2774 B::Shr => (l as i64).wrapping_shr(r as u32) as f32,
2775 }
2776}
2777
2778pub(crate) fn region_cast_scalar(to: rlx_ir::DType, v: f32) -> f32 {
2790 use rlx_ir::DType as DT;
2791 match to {
2792 DT::F32 | DT::F64 | DT::C64 | DT::C128 => v,
2793 DT::F16 => half::f16::from_f32(v).to_f32(),
2794 DT::BF16 => half::bf16::from_f32(v).to_f32(),
2795 DT::I8 => v as i8 as f32,
2796 DT::I16 => v as i16 as f32,
2797 DT::I32 => v as i32 as f32,
2798 DT::I64 => v as i64 as f32,
2799 DT::U8 => v as u8 as f32,
2800 DT::U32 => v as u32 as f32,
2801 DT::Bool => f32::from(u8::from(v != 0.0)),
2802 }
2803}
2804
2805#[inline]
2806pub(crate) fn region_compare_scalar(op: rlx_ir::op::CmpOp, l: f32, r: f32) -> bool {
2807 use rlx_ir::op::CmpOp as C;
2808 match op {
2809 C::Eq => l == r,
2810 C::Ne => l != r,
2811 C::Lt => l < r,
2812 C::Le => l <= r,
2813 C::Gt => l > r,
2814 C::Ge => l >= r,
2815 }
2816}
2817
2818#[inline]
2822pub(crate) fn region_resolve_operand(
2823 op: &rlx_ir::op::ChainOperand,
2824 gid: usize,
2825 base: *const u8,
2826 input_offs: &[usize],
2827 in_lanes: &[crate::thunk::types::LaneKind],
2828 scalar_mask: u32,
2829 modulus: &[u32; 16],
2830 scratch: &[f32; 32],
2831) -> f32 {
2832 use rlx_ir::op::ChainOperand as O;
2833 match op {
2834 O::Step(s) => scratch[*s as usize],
2835 O::Input(i) => {
2836 let i = *i as usize;
2837 let row = if (scalar_mask >> i) & 1 == 1 {
2838 0
2839 } else if modulus[i] != 0 {
2840 gid % modulus[i] as usize
2841 } else {
2842 gid
2843 };
2844 let lane = in_lanes
2847 .get(i)
2848 .copied()
2849 .unwrap_or(crate::thunk::types::LaneKind::F32);
2850 unsafe { lane.load(base, input_offs[i], row) }
2851 }
2852 }
2853}
2854
2855#[inline]
2858pub(crate) fn region_eval_elem(
2859 gid: usize,
2860 base: *const u8,
2861 input_offs: &[usize],
2862 in_lanes: &[crate::thunk::types::LaneKind],
2863 chain: &[rlx_ir::op::ChainStep],
2864 scalar_mask: u32,
2865 modulus: &[u32; 16],
2866) -> f32 {
2867 use rlx_ir::op::ChainStep as S;
2868 let mut scratch = [0f32; 32];
2869 let r = |o: &rlx_ir::op::ChainOperand, sc: &[f32; 32]| {
2870 region_resolve_operand(o, gid, base, input_offs, in_lanes, scalar_mask, modulus, sc)
2871 };
2872 for (k, step) in chain.iter().enumerate() {
2873 scratch[k] = match step {
2874 S::Activation(a, x) => region_activation_scalar(*a, r(x, &scratch)),
2875 S::Cast(to, x) => region_cast_scalar(*to, r(x, &scratch)),
2876 S::Binary(op, l, rr) => region_binary_scalar(*op, r(l, &scratch), r(rr, &scratch)),
2877 S::Compare(op, l, rr) => {
2878 if region_compare_scalar(*op, r(l, &scratch), r(rr, &scratch)) {
2879 1.0
2880 } else {
2881 0.0
2882 }
2883 }
2884 S::Where(c, t, f) => {
2885 if r(c, &scratch) != 0.0 {
2886 r(t, &scratch)
2887 } else {
2888 r(f, &scratch)
2889 }
2890 }
2891 };
2892 }
2893 scratch[chain.len() - 1]
2894}
2895
2896#[inline(always)]
2901pub(crate) fn apply_activation_inplace(d: &mut [f32], act: rlx_ir::op::Activation) {
2902 use rlx_ir::op::Activation;
2903 match act {
2904 Activation::Gelu => crate::kernels::par_gelu_inplace(d),
2905 Activation::GeluApprox => crate::kernels::par_gelu_approx_inplace(d),
2906 Activation::Silu => crate::kernels::par_silu_inplace(d),
2907 Activation::Relu => {
2908 for v in d.iter_mut() {
2909 *v = v.max(0.0);
2910 }
2911 }
2912 Activation::Sigmoid => {
2913 let x = d.to_vec();
2914 crate::vmath::vvsigmoidf(d, &x);
2915 }
2916 Activation::Tanh => crate::vmath::vvtanhf_hot_inplace(d),
2917 Activation::Exp => crate::vmath::vvexpf_hot_inplace(d),
2918 Activation::Log => crate::vmath::vvlogf_inplace(d),
2919 Activation::Sqrt => crate::vmath::vvsqrtf_inplace(d),
2920 Activation::Rsqrt => crate::vmath::vvrsqrtf_inplace(d),
2921 Activation::Neg => {
2922 for v in d.iter_mut() {
2923 *v = -*v;
2924 }
2925 }
2926 Activation::Abs => {
2927 for v in d.iter_mut() {
2928 *v = v.abs();
2929 }
2930 }
2931 Activation::Round => {
2932 for v in d.iter_mut() {
2933 *v = v.round();
2934 }
2935 }
2936 Activation::Sin => {
2937 for v in d.iter_mut() {
2938 *v = v.sin();
2939 }
2940 }
2941 Activation::Cos => {
2942 for v in d.iter_mut() {
2943 *v = v.cos();
2944 }
2945 }
2946 Activation::Tan => {
2947 for v in d.iter_mut() {
2948 *v = v.tan();
2949 }
2950 }
2951 Activation::Atan => {
2952 for v in d.iter_mut() {
2953 *v = v.atan();
2954 }
2955 }
2956 Activation::Recip => crate::vmath::vvrecf_inplace(d),
2957 Activation::Floor => {
2958 for v in d.iter_mut() {
2959 *v = v.floor();
2960 }
2961 }
2962 Activation::Ceil => {
2963 for v in d.iter_mut() {
2964 *v = v.ceil();
2965 }
2966 }
2967 Activation::Sign => {
2968 for v in d.iter_mut() {
2969 *v = if *v > 0.0 {
2970 1.0
2971 } else if *v < 0.0 {
2972 -1.0
2973 } else {
2974 0.0
2975 };
2976 }
2977 }
2978 Activation::Softplus => {
2979 for v in d.iter_mut() {
2980 *v = v.max(0.0) + (-(v.abs())).exp().ln_1p();
2981 }
2982 }
2983 Activation::Elu => {
2984 for v in d.iter_mut() {
2985 *v = if *v > 0.0 { *v } else { v.exp() - 1.0 };
2986 }
2987 }
2988 Activation::Erf => {
2989 for v in d.iter_mut() {
2990 *v = erf_f32(*v);
2991 }
2992 }
2993 Activation::HardSwish => {
2994 for v in d.iter_mut() {
2995 let x = *v;
2996 *v = x * (x + 3.0).clamp(0.0, 6.0) / 6.0;
2997 }
2998 }
2999 Activation::HardSigmoid => {
3000 for v in d.iter_mut() {
3001 *v = (*v / 6.0 + 0.5).clamp(0.0, 1.0);
3002 }
3003 }
3004 Activation::Mish => {
3005 for v in d.iter_mut() {
3006 let x = *v;
3007 *v = x * (x.max(0.0) + (-(x.abs())).exp().ln_1p()).tanh();
3008 }
3009 }
3010 Activation::Softsign => {
3011 for v in d.iter_mut() {
3012 let x = *v;
3013 *v = x / (1.0 + x.abs());
3014 }
3015 }
3016 Activation::LogSigmoid => {
3017 for v in d.iter_mut() {
3018 let x = *v;
3019 *v = x.min(0.0) - (-(x.abs())).exp().ln_1p();
3020 }
3021 }
3022 }
3023}
3024
3025pub(crate) fn activation_backward_kernel(
3026 act: rlx_ir::op::Activation,
3027 xs: &[f32],
3028 dys: &[f32],
3029 out: &mut [f32],
3030) {
3031 use rlx_ir::op::Activation;
3032 let n = xs.len();
3033 debug_assert_eq!(dys.len(), n);
3034 debug_assert_eq!(out.len(), n);
3035 match act {
3036 Activation::Relu => {
3037 for i in 0..n {
3038 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
3039 }
3040 }
3041 Activation::Sigmoid => {
3042 for i in 0..n {
3043 let s = 1.0 / (1.0 + (-xs[i]).exp());
3044 out[i] = s * (1.0 - s) * dys[i];
3045 }
3046 }
3047 Activation::Tanh => {
3048 for i in 0..n {
3049 let t = xs[i].tanh();
3050 out[i] = (1.0 - t * t) * dys[i];
3051 }
3052 }
3053 Activation::Silu => {
3054 for i in 0..n {
3056 let s = 1.0 / (1.0 + (-xs[i]).exp());
3057 out[i] = s * (1.0 + xs[i] * (1.0 - s)) * dys[i];
3058 }
3059 }
3060 Activation::Gelu => {
3061 const INV_SQRT2: f32 = 0.707_106_77;
3064 const INV_SQRT_2PI: f32 = 0.398_942_3;
3065 for i in 0..n {
3066 let x = xs[i];
3067 let phi = 0.5 * (1.0 + erf_f32(x * INV_SQRT2));
3068 let pdf = INV_SQRT_2PI * (-(x * x) * 0.5).exp();
3069 out[i] = (phi + x * pdf) * dys[i];
3070 }
3071 }
3072 Activation::GeluApprox => {
3073 const C: f32 = 0.797_884_6; const A: f32 = 0.044_715;
3077 for i in 0..n {
3078 let x = xs[i];
3079 let inner = C * (x + A * x * x * x);
3080 let t = inner.tanh();
3081 let dinner = C * (1.0 + 3.0 * A * x * x);
3082 let d = 0.5 * (1.0 + t) + 0.5 * x * (1.0 - t * t) * dinner;
3083 out[i] = d * dys[i];
3084 }
3085 }
3086 Activation::Exp => {
3087 for i in 0..n {
3088 out[i] = xs[i].exp() * dys[i];
3089 }
3090 }
3091 Activation::Log => {
3092 for i in 0..n {
3093 out[i] = dys[i] / xs[i];
3094 }
3095 }
3096 Activation::Sqrt => {
3097 for i in 0..n {
3099 let s = xs[i].sqrt();
3100 out[i] = if s > 0.0 { 0.5 * dys[i] / s } else { 0.0 };
3101 }
3102 }
3103 Activation::Rsqrt => {
3104 for i in 0..n {
3106 let s = xs[i].sqrt();
3107 out[i] = if s > 0.0 {
3108 -0.5 * dys[i] / (xs[i] * s)
3109 } else {
3110 0.0
3111 };
3112 }
3113 }
3114 Activation::Neg => {
3115 for i in 0..n {
3116 out[i] = -dys[i];
3117 }
3118 }
3119 Activation::Abs => {
3120 for i in 0..n {
3122 let x = xs[i];
3123 let s = if x > 0.0 {
3124 1.0
3125 } else if x < 0.0 {
3126 -1.0
3127 } else {
3128 0.0
3129 };
3130 out[i] = s * dys[i];
3131 }
3132 }
3133 Activation::Round => {
3134 out.copy_from_slice(dys);
3139 }
3140 Activation::Sin => {
3141 for i in 0..n {
3143 out[i] = xs[i].cos() * dys[i];
3144 }
3145 }
3146 Activation::Cos => {
3147 for i in 0..n {
3148 out[i] = -xs[i].sin() * dys[i];
3149 }
3150 }
3151 Activation::Tan => {
3152 for i in 0..n {
3154 let t = xs[i].tan();
3155 out[i] = (1.0 + t * t) * dys[i];
3156 }
3157 }
3158 Activation::Atan => {
3159 for i in 0..n {
3161 let x = xs[i];
3162 out[i] = dys[i] / (1.0 + x * x);
3163 }
3164 }
3165 Activation::Recip => {
3166 for i in 0..n {
3167 out[i] = -dys[i] / (xs[i] * xs[i]);
3168 }
3169 }
3170 Activation::Floor | Activation::Ceil | Activation::Sign => {
3172 for o in out.iter_mut() {
3173 *o = 0.0;
3174 }
3175 }
3176 Activation::Softplus => {
3177 for i in 0..n {
3178 out[i] = dys[i] / (1.0 + (-xs[i]).exp());
3179 }
3180 }
3181 Activation::Elu => {
3182 for i in 0..n {
3183 out[i] = if xs[i] > 0.0 {
3184 dys[i]
3185 } else {
3186 dys[i] * xs[i].exp()
3187 };
3188 }
3189 }
3190 Activation::Erf
3192 | Activation::HardSwish
3193 | Activation::HardSigmoid
3194 | Activation::Mish
3195 | Activation::Softsign
3196 | Activation::LogSigmoid => {
3197 panic!("activation_backward_kernel: {act:?} is decomposed at the AD level")
3198 }
3199 }
3200}
3201
3202pub(crate) fn activation_backward_kernel_f64(
3206 act: rlx_ir::op::Activation,
3207 xs: &[f64],
3208 dys: &[f64],
3209 out: &mut [f64],
3210) {
3211 use rlx_ir::op::Activation;
3212 let n = xs.len();
3213 debug_assert_eq!(dys.len(), n);
3214 debug_assert_eq!(out.len(), n);
3215 match act {
3216 Activation::Relu => {
3217 for i in 0..n {
3218 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
3219 }
3220 }
3221 Activation::Sigmoid => {
3222 for i in 0..n {
3223 let s = 1.0 / (1.0 + (-xs[i]).exp());
3224 out[i] = s * (1.0 - s) * dys[i];
3225 }
3226 }
3227 Activation::Tanh => {
3228 for i in 0..n {
3229 let t = xs[i].tanh();
3230 out[i] = (1.0 - t * t) * dys[i];
3231 }
3232 }
3233 Activation::Silu => {
3234 for i in 0..n {
3235 let s = 1.0 / (1.0 + (-xs[i]).exp());
3236 out[i] = s * (1.0 + xs[i] * (1.0 - s)) * dys[i];
3237 }
3238 }
3239 Activation::Gelu | Activation::GeluApprox => {
3240 const INV_SQRT2: f64 = std::f64::consts::FRAC_1_SQRT_2;
3242 const INV_SQRT_2PI: f64 = 0.398_942_280_401_432_7;
3243 for i in 0..n {
3244 let x = xs[i];
3245 let phi = 0.5 * (1.0 + erf_f64(x * INV_SQRT2));
3246 let pdf = INV_SQRT_2PI * (-(x * x) * 0.5).exp();
3247 out[i] = (phi + x * pdf) * dys[i];
3248 }
3249 }
3250 Activation::Exp => {
3251 for i in 0..n {
3252 out[i] = xs[i].exp() * dys[i];
3253 }
3254 }
3255 Activation::Log => {
3256 for i in 0..n {
3257 out[i] = dys[i] / xs[i];
3258 }
3259 }
3260 Activation::Sqrt => {
3261 for i in 0..n {
3262 let s = xs[i].sqrt();
3263 out[i] = if s > 0.0 { 0.5 * dys[i] / s } else { 0.0 };
3264 }
3265 }
3266 Activation::Rsqrt => {
3267 for i in 0..n {
3268 let s = xs[i].sqrt();
3269 out[i] = if s > 0.0 {
3270 -0.5 * dys[i] / (xs[i] * s)
3271 } else {
3272 0.0
3273 };
3274 }
3275 }
3276 Activation::Neg => {
3277 for i in 0..n {
3278 out[i] = -dys[i];
3279 }
3280 }
3281 Activation::Abs => {
3282 for i in 0..n {
3283 let x = xs[i];
3284 let s = if x > 0.0 {
3285 1.0
3286 } else if x < 0.0 {
3287 -1.0
3288 } else {
3289 0.0
3290 };
3291 out[i] = s * dys[i];
3292 }
3293 }
3294 Activation::Round => {
3295 out.copy_from_slice(dys);
3296 }
3297 Activation::Sin => {
3298 for i in 0..n {
3299 out[i] = xs[i].cos() * dys[i];
3300 }
3301 }
3302 Activation::Cos => {
3303 for i in 0..n {
3304 out[i] = -xs[i].sin() * dys[i];
3305 }
3306 }
3307 Activation::Tan => {
3308 for i in 0..n {
3309 let t = xs[i].tan();
3310 out[i] = (1.0 + t * t) * dys[i];
3311 }
3312 }
3313 Activation::Atan => {
3314 for i in 0..n {
3315 let x = xs[i];
3316 out[i] = dys[i] / (1.0 + x * x);
3317 }
3318 }
3319 Activation::Recip => {
3320 for i in 0..n {
3321 out[i] = -dys[i] / (xs[i] * xs[i]);
3322 }
3323 }
3324 Activation::Floor | Activation::Ceil | Activation::Sign => {
3326 for o in out.iter_mut() {
3327 *o = 0.0;
3328 }
3329 }
3330 Activation::Softplus => {
3331 for i in 0..n {
3332 out[i] = dys[i] / (1.0 + (-xs[i]).exp());
3333 }
3334 }
3335 Activation::Elu => {
3336 for i in 0..n {
3337 out[i] = if xs[i] > 0.0 {
3338 dys[i]
3339 } else {
3340 dys[i] * xs[i].exp()
3341 };
3342 }
3343 }
3344 Activation::Erf
3346 | Activation::HardSwish
3347 | Activation::HardSigmoid
3348 | Activation::Mish
3349 | Activation::Softsign
3350 | Activation::LogSigmoid => {
3351 panic!("activation_backward_kernel: {act:?} is decomposed at the AD level")
3352 }
3353 }
3354}
3355
3356#[inline(always)]
3361pub(crate) fn erf_f64(x: f64) -> f64 {
3362 let s = x.signum();
3363 let x = x.abs();
3364 let t = 1.0 / (1.0 + 0.327_591_1 * x);
3365 let y = 1.0
3366 - (((((1.061_405_43 * t - 1.453_152_03) * t) + 1.421_413_75) * t - 0.284_496_74) * t
3367 + 0.254_829_59)
3368 * t
3369 * (-x * x).exp();
3370 s * y
3371}
3372
3373#[inline(always)]
3376pub(crate) fn erf_f32(x: f32) -> f32 {
3377 let s = x.signum();
3378 let x = x.abs();
3379 let t = 1.0 / (1.0 + 0.327_591_1 * x);
3380 let y = 1.0
3381 - (((((1.061_405_4 * t - 1.453_152_1) * t) + 1.421_413_8) * t - 0.284_496_74) * t
3382 + 0.254_829_6)
3383 * t
3384 * (-x * x).exp();
3385 s * y
3386}
3387
3388pub(crate) fn narrow_thunk_closure(
3389 src: usize,
3390 dst: usize,
3391 outer: u32,
3392 src_stride: u32,
3393 dst_stride: u32,
3394 inner: u32,
3395 elem_bytes: u8,
3396) -> Arc<dyn Fn(*mut u8) + Send + Sync> {
3397 let (outer, ss, ds, inner, eb) = (
3398 outer as usize,
3399 src_stride as usize,
3400 dst_stride as usize,
3401 inner as usize,
3402 elem_bytes as usize,
3403 );
3404 let row_bytes = inner.saturating_mul(eb);
3405 let src_row_stride = ss.saturating_mul(eb);
3406 let dst_row_stride = ds.saturating_mul(eb);
3407 Arc::new(move |base: *mut u8| unsafe {
3408 if row_bytes == 0 || src == dst {
3409 return;
3410 }
3411 let arena_len = usize::MAX;
3413 for o in 0..outer {
3414 let s_off = src + o * src_row_stride;
3415 let d_off = dst + o * dst_row_stride;
3416 if s_off == d_off {
3417 continue;
3418 }
3419 if s_off.saturating_add(row_bytes) > arena_len
3420 || d_off.saturating_add(row_bytes) > arena_len
3421 {
3422 break;
3423 }
3424 std::ptr::copy_nonoverlapping(base.add(s_off), base.add(d_off), row_bytes);
3425 }
3426 })
3427}
3428
3429pub(crate) fn transpose_walk_f64(
3433 inp: &[f64],
3434 out: &mut [f64],
3435 out_dims: &[u32],
3436 in_strides: &[u32],
3437) {
3438 let rank = out_dims.len();
3439 let mut idx = vec![0u32; rank];
3440 for o in 0..out.len() {
3441 let mut src_off = 0usize;
3442 for d in 0..rank {
3443 src_off += idx[d] as usize * in_strides[d] as usize;
3444 }
3445 out[o] = inp[broadcast_src_index(src_off, inp.len())];
3446 for d in (0..rank).rev() {
3448 idx[d] += 1;
3449 if idx[d] < out_dims[d] {
3450 break;
3451 }
3452 idx[d] = 0;
3453 }
3454 }
3455}
3456
3457pub(crate) fn apply_activation_f64(inp: &[f64], out: &mut [f64], kind: Activation) {
3463 match kind {
3464 Activation::Neg => {
3465 for (o, &v) in out.iter_mut().zip(inp) {
3466 *o = -v;
3467 }
3468 }
3469 Activation::Exp => {
3470 for (o, &v) in out.iter_mut().zip(inp) {
3471 *o = v.exp();
3472 }
3473 }
3474 Activation::Log => {
3475 for (o, &v) in out.iter_mut().zip(inp) {
3476 *o = v.ln();
3477 }
3478 }
3479 Activation::Sqrt => {
3480 for (o, &v) in out.iter_mut().zip(inp) {
3481 *o = v.sqrt();
3482 }
3483 }
3484 Activation::Rsqrt => {
3485 for (o, &v) in out.iter_mut().zip(inp) {
3486 *o = 1.0 / v.sqrt();
3487 }
3488 }
3489 Activation::Abs => {
3490 for (o, &v) in out.iter_mut().zip(inp) {
3491 *o = v.abs();
3492 }
3493 }
3494 Activation::Tanh => {
3495 for (o, &v) in out.iter_mut().zip(inp) {
3496 *o = v.tanh();
3497 }
3498 }
3499 Activation::Sigmoid => {
3500 for (o, &v) in out.iter_mut().zip(inp) {
3501 *o = 1.0 / (1.0 + (-v).exp());
3502 }
3503 }
3504 Activation::Relu => {
3505 for (o, &v) in out.iter_mut().zip(inp) {
3506 *o = v.max(0.0);
3507 }
3508 }
3509 Activation::Round => {
3510 for (o, &v) in out.iter_mut().zip(inp) {
3511 *o = v.round_ties_even();
3512 }
3513 }
3514 Activation::Sin => {
3515 for (o, &v) in out.iter_mut().zip(inp) {
3516 *o = v.sin();
3517 }
3518 }
3519 Activation::Cos => {
3520 for (o, &v) in out.iter_mut().zip(inp) {
3521 *o = v.cos();
3522 }
3523 }
3524 Activation::Tan => {
3525 for (o, &v) in out.iter_mut().zip(inp) {
3526 *o = v.tan();
3527 }
3528 }
3529 Activation::Atan => {
3530 for (o, &v) in out.iter_mut().zip(inp) {
3531 *o = v.atan();
3532 }
3533 }
3534 Activation::Recip => {
3535 for (o, &v) in out.iter_mut().zip(inp) {
3536 *o = 1.0 / v;
3537 }
3538 }
3539 Activation::Floor => {
3540 for (o, &v) in out.iter_mut().zip(inp) {
3541 *o = v.floor();
3542 }
3543 }
3544 Activation::Ceil => {
3545 for (o, &v) in out.iter_mut().zip(inp) {
3546 *o = v.ceil();
3547 }
3548 }
3549 Activation::Sign => {
3550 for (o, &v) in out.iter_mut().zip(inp) {
3551 *o = if v > 0.0 {
3552 1.0
3553 } else if v < 0.0 {
3554 -1.0
3555 } else {
3556 0.0
3557 };
3558 }
3559 }
3560 Activation::Softplus => {
3561 for (o, &v) in out.iter_mut().zip(inp) {
3562 *o = v.max(0.0) + (-(v.abs())).exp().ln_1p();
3563 }
3564 }
3565 Activation::Elu => {
3566 for (o, &v) in out.iter_mut().zip(inp) {
3567 *o = if v > 0.0 { v } else { v.exp() - 1.0 };
3568 }
3569 }
3570 Activation::Erf => {
3571 for (o, &v) in out.iter_mut().zip(inp) {
3572 *o = erf_f64(v);
3573 }
3574 }
3575 Activation::HardSwish => {
3576 for (o, &v) in out.iter_mut().zip(inp) {
3577 *o = v * (v + 3.0).clamp(0.0, 6.0) / 6.0;
3578 }
3579 }
3580 Activation::HardSigmoid => {
3581 for (o, &v) in out.iter_mut().zip(inp) {
3582 *o = (v / 6.0 + 0.5).clamp(0.0, 1.0);
3583 }
3584 }
3585 Activation::Mish => {
3586 for (o, &v) in out.iter_mut().zip(inp) {
3587 *o = v * (v.max(0.0) + (-(v.abs())).exp().ln_1p()).tanh();
3588 }
3589 }
3590 Activation::Softsign => {
3591 for (o, &v) in out.iter_mut().zip(inp) {
3592 *o = v / (1.0 + v.abs());
3593 }
3594 }
3595 Activation::LogSigmoid => {
3596 for (o, &v) in out.iter_mut().zip(inp) {
3597 *o = v.min(0.0) - (-(v.abs())).exp().ln_1p();
3598 }
3599 }
3600 Activation::Gelu | Activation::GeluApprox | Activation::Silu => {
3601 panic!(
3602 "apply_activation_f64: {kind:?} not yet implemented at f64. \
3603 Add when a workload needs it."
3604 );
3605 }
3606 }
3607}
3608
3609#[inline]
3610pub(crate) fn binary_op_f64(op: BinaryOp, a: f64, b: f64) -> f64 {
3611 match op {
3612 BinaryOp::Add => a + b,
3613 BinaryOp::Sub => a - b,
3614 BinaryOp::Mul => a * b,
3615 BinaryOp::Div => a / b,
3616 BinaryOp::Max => a.max(b),
3617 BinaryOp::Min => a.min(b),
3618 BinaryOp::Pow => a.powf(b),
3619 BinaryOp::Mod => a % b,
3620 BinaryOp::Atan2 => a.atan2(b),
3621 BinaryOp::BitAnd => ((a as i64) & (b as i64)) as f64,
3622 BinaryOp::BitOr => ((a as i64) | (b as i64)) as f64,
3623 BinaryOp::BitXor => ((a as i64) ^ (b as i64)) as f64,
3624 BinaryOp::Shl => ((a as i64) << (b as i64)) as f64,
3625 BinaryOp::Shr => ((a as i64) >> (b as i64)) as f64,
3626 }
3627}