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 half_slot_is_widened(graph, arena, node.id)
211 && matches!(out_dtype, rlx_ir::DType::BF16 | rlx_ir::DType::F16)
212 && in_dtype == rlx_ir::DType::F32
213 {
214 Thunk::RoundHalf {
215 src,
216 dst,
217 len: len as u32,
218 bf16: out_dtype == rlx_ir::DType::BF16,
219 }
220 } else if matches!(in_dtype, rlx_ir::DType::BF16 | rlx_ir::DType::F16)
221 && out_dtype == rlx_ir::DType::F32
222 && half_slot_is_widened(graph, arena, node.inputs[0])
223 {
224 Thunk::Copy {
226 src,
227 dst,
228 len: len as u32,
229 }
230 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::F16 {
231 Thunk::CastF32ToF16 {
233 src,
234 dst,
235 len: len as u32,
236 }
237 } else if in_dtype == rlx_ir::DType::F16 && out_dtype == rlx_ir::DType::F32 {
238 Thunk::CastF16ToF32 {
240 src,
241 dst,
242 len: len as u32,
243 }
244 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::BF16 {
245 Thunk::CastF32ToBf16 {
247 src,
248 dst,
249 len: len as u32,
250 }
251 } else if in_dtype == rlx_ir::DType::BF16 && out_dtype == rlx_ir::DType::F32 {
252 Thunk::CastBf16ToF32 {
254 src,
255 dst,
256 len: len as u32,
257 }
258 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::I32 {
259 Thunk::CastF32ToI32 {
260 src,
261 dst,
262 len: len as u32,
263 }
264 } else if in_dtype == rlx_ir::DType::I64 && out_dtype == rlx_ir::DType::F32 {
265 Thunk::CastI64ToF32 {
266 src,
267 dst,
268 len: len as u32,
269 }
270 } else if in_dtype == rlx_ir::DType::Bool && out_dtype == rlx_ir::DType::I32 {
271 Thunk::CastBoolToI32 {
272 src,
273 dst,
274 len: len as u32,
275 }
276 } else if in_dtype == rlx_ir::DType::Bool && out_dtype == rlx_ir::DType::F32 {
277 Thunk::CastBoolToF32 {
280 src,
281 dst,
282 len: len as u32,
283 }
284 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::Bool {
285 Thunk::CastF32ToBool {
288 src,
289 dst,
290 len: len as u32,
291 }
292 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::F32 {
293 Thunk::CastI32ToF32 {
294 src,
295 dst,
296 len: len as u32,
297 }
298 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::I64 {
299 Thunk::CastI32ToI64 {
300 src,
301 dst,
302 len: len as u32,
303 }
304 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::Bool {
305 Thunk::CastI32ToBool {
306 src,
307 dst,
308 len: len as u32,
309 }
310 } else if in_dtype == rlx_ir::DType::I64 && out_dtype == rlx_ir::DType::Bool {
311 Thunk::CastI64ToBool {
312 src,
313 dst,
314 len: len as u32,
315 }
316 } else if in_dtype == rlx_ir::DType::Bool && out_dtype == rlx_ir::DType::I64 {
317 Thunk::CastBoolToI64 {
318 src,
319 dst,
320 len: len as u32,
321 }
322 } else if in_dtype == out_dtype {
323 match out_dtype {
324 rlx_ir::DType::F64 => Thunk::CopyF64 {
325 src,
326 dst,
327 len: len as u32,
328 },
329 rlx_ir::DType::I64 => Thunk::CopyI64 {
330 src,
331 dst,
332 len: len as u32,
333 },
334 rlx_ir::DType::C128 => Thunk::CastGeneric {
339 src,
340 dst,
341 len: len as u32,
342 src_dtype: in_dtype,
343 dst_dtype: out_dtype,
344 },
345 _ => Thunk::Copy {
346 src,
347 dst,
348 len: len as u32,
349 },
350 }
351 } else {
352 Thunk::CastGeneric {
358 src,
359 dst,
360 len: len as u32,
361 src_dtype: in_dtype,
362 dst_dtype: out_dtype,
363 }
364 }
365 }
366}
367
368#[derive(Clone, Copy)]
374enum CastScalar {
375 F(f64),
376 I(i64),
377 C(f64, f64),
378}
379
380impl CastScalar {
381 #[inline]
384 fn real(self) -> f64 {
385 match self {
386 CastScalar::F(f) => f,
387 CastScalar::I(i) => i as f64,
388 CastScalar::C(re, _) => re,
389 }
390 }
391
392 #[inline]
395 fn truthy(self) -> bool {
396 match self {
397 CastScalar::F(f) => f != 0.0,
398 CastScalar::I(i) => i != 0,
399 CastScalar::C(re, im) => re != 0.0 || im != 0.0,
400 }
401 }
402
403 #[inline]
406 fn complex(self) -> (f32, f32) {
407 match self {
408 CastScalar::F(f) => (f as f32, 0.0),
409 CastScalar::I(i) => (i as f32, 0.0),
410 CastScalar::C(re, im) => (re as f32, im as f32),
411 }
412 }
413
414 #[inline]
418 fn complex_f64(self) -> (f64, f64) {
419 match self {
420 CastScalar::F(f) => (f, 0.0),
421 CastScalar::I(i) => (i as f64, 0.0),
422 CastScalar::C(re, im) => (re, im),
423 }
424 }
425}
426
427#[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn exec_cast_generic(
446 src: usize,
447 dst: usize,
448 len: usize,
449 src_dtype: rlx_ir::DType,
450 dst_dtype: rlx_ir::DType,
451 base: *mut u8,
452) {
453 use rlx_ir::DType as DT;
454 if len == 0 {
455 return;
456 }
457 let mut vals: Vec<CastScalar> = Vec::with_capacity(len);
461 unsafe {
462 match src_dtype {
463 DT::F32 => {
464 let s = sl(src, base, len);
465 for i in 0..len {
466 vals.push(CastScalar::F(s[i] as f64));
467 }
468 }
469 DT::F64 => {
470 let s = sl_f64(src, base, len);
471 for i in 0..len {
472 vals.push(CastScalar::F(s[i]));
473 }
474 }
475 DT::F16 => {
476 let s = sl_f16(src, base, len);
477 for i in 0..len {
478 vals.push(CastScalar::F(s[i].to_f32() as f64));
479 }
480 }
481 DT::BF16 => {
482 let s = sl_bf16(src, base, len);
483 for i in 0..len {
484 vals.push(CastScalar::F(s[i].to_f32() as f64));
485 }
486 }
487 DT::I8 => {
488 let s = sl_i8(src, base, len);
489 for i in 0..len {
490 vals.push(CastScalar::I(s[i] as i64));
491 }
492 }
493 DT::I16 => {
494 let s = sl_i16(src, base, len);
495 for i in 0..len {
496 vals.push(CastScalar::I(s[i] as i64));
497 }
498 }
499 DT::I32 => {
500 let s = sl_i32(src, base, len);
501 for i in 0..len {
502 vals.push(CastScalar::I(s[i] as i64));
503 }
504 }
505 DT::I64 => {
506 let s = sl_i64(src, base, len);
507 for i in 0..len {
508 vals.push(CastScalar::I(s[i]));
509 }
510 }
511 DT::U8 => {
512 let s = sl_u8(src, base, len);
513 for i in 0..len {
514 vals.push(CastScalar::I(s[i] as i64));
515 }
516 }
517 DT::U32 => {
518 let s = sl_u32(src, base, len);
519 for i in 0..len {
520 vals.push(CastScalar::I(s[i] as i64));
521 }
522 }
523 DT::Bool => {
524 let s = sl_u8(src, base, len);
525 for i in 0..len {
526 vals.push(CastScalar::I(i64::from(s[i] != 0)));
527 }
528 }
529 DT::C64 => {
530 let s = sl(src, base, 2 * len);
532 for i in 0..len {
533 vals.push(CastScalar::C(s[2 * i] as f64, s[2 * i + 1] as f64));
534 }
535 }
536 DT::C128 => {
537 let s = sl_f64(src, base, 2 * len);
539 for i in 0..len {
540 vals.push(CastScalar::C(s[2 * i], s[2 * i + 1]));
541 }
542 }
543 }
544
545 macro_rules! write_int {
548 ($accessor:ident, $t:ty) => {{
549 let o = $accessor(dst, base, len);
550 for i in 0..len {
551 o[i] = match vals[i] {
552 CastScalar::I(n) => n as $t,
553 CastScalar::F(f) => f as $t,
554 CastScalar::C(re, _) => re as $t,
555 };
556 }
557 }};
558 }
559 match dst_dtype {
560 DT::F32 => {
561 let o = sl_mut(dst, base, len);
562 for i in 0..len {
563 o[i] = vals[i].real() as f32;
564 }
565 }
566 DT::F64 => {
567 let o = sl_mut_f64(dst, base, len);
568 for i in 0..len {
569 o[i] = vals[i].real();
570 }
571 }
572 DT::F16 => {
573 let o = sl_mut_f16(dst, base, len);
574 for i in 0..len {
575 o[i] = half::f16::from_f32(vals[i].real() as f32);
576 }
577 }
578 DT::BF16 => {
579 let o = sl_mut_bf16(dst, base, len);
580 for i in 0..len {
581 o[i] = half::bf16::from_f32(vals[i].real() as f32);
582 }
583 }
584 DT::I8 => write_int!(sl_mut_i8, i8),
585 DT::I16 => write_int!(sl_mut_i16, i16),
586 DT::I32 => write_int!(sl_mut_i32, i32),
587 DT::I64 => write_int!(sl_mut_i64, i64),
588 DT::U8 => write_int!(sl_mut_u8, u8),
589 DT::U32 => write_int!(sl_mut_u32, u32),
590 DT::Bool => {
591 let o = sl_mut_u8(dst, base, len);
592 for i in 0..len {
593 o[i] = u8::from(vals[i].truthy());
594 }
595 }
596 DT::C64 => {
597 let o = sl_mut(dst, base, 2 * len);
599 for i in 0..len {
600 let (re, im) = vals[i].complex();
601 o[2 * i] = re;
602 o[2 * i + 1] = im;
603 }
604 }
605 DT::C128 => {
606 let o = sl_mut_f64(dst, base, 2 * len);
608 for i in 0..len {
609 let (re, im) = vals[i].complex_f64();
610 o[2 * i] = re;
611 o[2 * i + 1] = im;
612 }
613 }
614 }
615 }
616}
617
618#[allow(unused_variables)]
619pub(crate) fn compile_expand(
620 node: &rlx_ir::Node,
621 graph: &Graph,
622 arena: &crate::arena::Arena,
623 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
624 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
625 rng: rlx_ir::RngOptions,
626) -> Thunk {
627 let Op::Expand { .. } = &node.op else {
628 unreachable!()
629 };
630 {
631 let in_shape = &graph.node(node.inputs[0]).shape;
636 let out_shape = &node.shape;
637 let in_rank = in_shape.rank();
638 let out_rank = out_shape.rank();
639 let pad = out_rank.saturating_sub(in_rank);
641 let in_dims: Vec<usize> = (0..out_rank)
642 .map(|i| {
643 if i < pad {
644 1
645 } else {
646 in_shape.dim(i - pad).unwrap_static()
647 }
648 })
649 .collect();
650 let mut in_strides_full = vec![1usize; out_rank];
652 for d in (0..out_rank.saturating_sub(1)).rev() {
653 in_strides_full[d] = in_strides_full[d + 1] * in_dims[d + 1];
654 }
655 let out_dims: Vec<u32> = (0..out_rank)
656 .map(|i| out_shape.dim(i).unwrap_static() as u32)
657 .collect();
658 let in_strides: Vec<u32> = (0..out_rank)
660 .map(|i| {
661 if in_dims[i] == 1 && (out_dims[i] as usize) > 1 {
662 0
663 } else {
664 in_strides_full[i] as u32
665 }
666 })
667 .collect();
668 let in_total = in_dims.iter().product::<usize>() as u32;
669 let src = node_offset(arena, node.inputs[0]);
670 let dst = node_offset(arena, node.id);
671 let elem_bytes = node.shape.dtype().size_bytes() as u8;
672 match node.shape.dtype() {
673 rlx_ir::DType::F64 => Thunk::TransposeF64 {
674 src,
675 dst,
676 in_total,
677 out_dims,
678 in_strides,
679 },
680 _ => Thunk::Transpose {
681 src,
682 dst,
683 in_total,
684 out_dims,
685 in_strides,
686 elem_bytes,
687 },
688 }
689 }
690}
691
692#[allow(unused_variables)]
693pub(crate) fn compile_transpose(
694 node: &rlx_ir::Node,
695 graph: &Graph,
696 arena: &crate::arena::Arena,
697 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
698 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
699 rng: rlx_ir::RngOptions,
700) -> Thunk {
701 let Op::Transpose { perm } = &node.op else {
702 unreachable!()
703 };
704 {
705 let in_shape = &graph.node(node.inputs[0]).shape;
708 let in_rank = in_shape.rank();
709 if perm.iter().any(|&p| p >= in_rank) {
710 Thunk::Nop
711 } else {
712 let in_dims: Vec<usize> = (0..in_rank)
713 .map(|i| in_shape.dim(i).unwrap_static())
714 .collect();
715 let mut in_strides_full = vec![1usize; in_rank];
717 for d in (0..in_rank.saturating_sub(1)).rev() {
718 in_strides_full[d] = in_strides_full[d + 1] * in_dims[d + 1];
719 }
720 let out_dims: Vec<u32> = perm.iter().map(|&p| in_dims[p] as u32).collect();
721 let in_strides: Vec<u32> = perm.iter().map(|&p| in_strides_full[p] as u32).collect();
722 let in_total = in_dims.iter().product::<usize>() as u32;
723 let src = node_offset(arena, node.inputs[0]);
724 let dst = node_offset(arena, node.id);
725 let elem_bytes = node.shape.dtype().size_bytes() as u8;
726 match node.shape.dtype() {
727 rlx_ir::DType::F64 => Thunk::TransposeF64 {
728 src,
729 dst,
730 in_total,
731 out_dims,
732 in_strides,
733 },
734 _ => Thunk::Transpose {
735 src,
736 dst,
737 in_total,
738 out_dims,
739 in_strides,
740 elem_bytes,
741 },
742 }
743 }
744 }
745}
746
747#[allow(unused_variables)]
748pub(crate) fn compile_scatter_add(
749 node: &rlx_ir::Node,
750 graph: &Graph,
751 arena: &crate::arena::Arena,
752 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
753 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
754 rng: rlx_ir::RngOptions,
755) -> Thunk {
756 let Op::ScatterAdd { axis } = &node.op else {
757 unreachable!()
758 };
759 assert_eq!(
764 *axis, 0,
765 "rlx-cpu: ScatterAdd axis {axis} reached the backend; run LowerScatterAddAxis first"
766 );
767 {
768 let upd_shape = &graph.node(node.inputs[0]).shape;
771 let out_shape = &node.shape;
772 let num_updates = upd_shape.dim(0).unwrap_static();
773 let out_dim = out_shape.dim(0).unwrap_static();
774 let trailing: usize = (1..out_shape.rank())
775 .map(|i| out_shape.dim(i).unwrap_static())
776 .product::<usize>()
777 .max(1);
778 Thunk::ScatterAdd {
779 updates: node_offset(arena, node.inputs[0]),
780 indices: node_offset(arena, node.inputs[1]),
781 dst: node_offset(arena, node.id),
782 num_updates: num_updates as u32,
783 out_dim: out_dim as u32,
784 trailing: trailing as u32,
785 }
786 }
787}
788
789#[allow(unused_variables)]
790pub(crate) fn compile_scatter_nd(
791 node: &rlx_ir::Node,
792 graph: &Graph,
793 arena: &crate::arena::Arena,
794 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
795 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
796 rng: rlx_ir::RngOptions,
797) -> Thunk {
798 let Op::ScatterNd { reduction } = &node.op else {
799 unreachable!()
800 };
801 let data_shape = &graph.node(node.inputs[0]).shape;
802 let indices_shape = &graph.node(node.inputs[1]).shape;
803 let updates_shape = &graph.node(node.inputs[2]).shape;
804 let data_len = data_shape.num_elements().unwrap_or(0);
805 let updates_len = updates_shape.num_elements().unwrap_or(0);
806 let indices_len = indices_shape.num_elements().unwrap_or(0);
807 let indices_i64 = u8::from(indices_shape.dtype() == rlx_ir::DType::I64);
808 Thunk::ScatterNd {
809 data: node_offset(arena, node.inputs[0]),
810 indices: node_offset(arena, node.inputs[1]),
811 updates: node_offset(arena, node.inputs[2]),
812 dst: node_offset(arena, node.id),
813 data_shape: (0..data_shape.rank())
814 .map(|i| data_shape.dim(i).unwrap_static() as u32)
815 .collect(),
816 indices_shape: (0..indices_shape.rank())
817 .map(|i| indices_shape.dim(i).unwrap_static() as u32)
818 .collect(),
819 data_len: data_len as u32,
820 updates_len: updates_len as u32,
821 indices_len: indices_len as u32,
822 indices_i64,
823 reduction: *reduction,
824 }
825}
826
827fn shape_u32s(shape: &rlx_ir::Shape) -> Vec<u32> {
828 (0..shape.rank())
829 .map(|i| shape.dim(i).unwrap_static() as u32)
830 .collect()
831}
832
833#[allow(unused_variables)]
834pub(crate) fn compile_scatter_elements(
835 node: &rlx_ir::Node,
836 graph: &Graph,
837 arena: &crate::arena::Arena,
838 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
839 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
840 rng: rlx_ir::RngOptions,
841) -> Thunk {
842 let Op::ScatterElements { axis, reduction } = &node.op else {
843 unreachable!()
844 };
845 let data_shape = &graph.node(node.inputs[0]).shape;
846 let indices_shape = &graph.node(node.inputs[1]).shape;
847 let updates_shape = &graph.node(node.inputs[2]).shape;
848 Thunk::ScatterElements {
849 data: node_offset(arena, node.inputs[0]),
850 indices: node_offset(arena, node.inputs[1]),
851 updates: node_offset(arena, node.inputs[2]),
852 dst: node_offset(arena, node.id),
853 data_shape: shape_u32s(data_shape),
854 indices_shape: shape_u32s(indices_shape),
855 data_len: data_shape.num_elements().unwrap_or(0) as u32,
856 updates_len: updates_shape.num_elements().unwrap_or(0) as u32,
857 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
858 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
859 axis: *axis,
860 reduction: *reduction,
861 }
862}
863
864#[allow(unused_variables)]
865pub(crate) fn compile_gather_nd(
866 node: &rlx_ir::Node,
867 graph: &Graph,
868 arena: &crate::arena::Arena,
869 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
870 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
871 rng: rlx_ir::RngOptions,
872) -> Thunk {
873 let Op::GatherNd { batch_dims } = &node.op else {
874 unreachable!()
875 };
876 let data_shape = &graph.node(node.inputs[0]).shape;
877 let indices_shape = &graph.node(node.inputs[1]).shape;
878 Thunk::GatherNd {
879 data: node_offset(arena, node.inputs[0]),
880 indices: node_offset(arena, node.inputs[1]),
881 dst: node_offset(arena, node.id),
882 data_shape: shape_u32s(data_shape),
883 indices_shape: shape_u32s(indices_shape),
884 data_len: data_shape.num_elements().unwrap_or(0) as u32,
885 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
886 out_len: node.shape.num_elements().unwrap_or(0) as u32,
887 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
888 batch_dims: *batch_dims,
889 }
890}
891
892#[allow(unused_variables)]
893pub(crate) fn compile_gather_elements(
894 node: &rlx_ir::Node,
895 graph: &Graph,
896 arena: &crate::arena::Arena,
897 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
898 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
899 rng: rlx_ir::RngOptions,
900) -> Thunk {
901 let Op::GatherElements { axis } = &node.op else {
902 unreachable!()
903 };
904 let data_shape = &graph.node(node.inputs[0]).shape;
905 let indices_shape = &graph.node(node.inputs[1]).shape;
906 Thunk::GatherElements {
907 data: node_offset(arena, node.inputs[0]),
908 indices: node_offset(arena, node.inputs[1]),
909 dst: node_offset(arena, node.id),
910 data_shape: shape_u32s(data_shape),
911 indices_shape: shape_u32s(indices_shape),
912 data_len: data_shape.num_elements().unwrap_or(0) as u32,
913 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
914 out_len: node.shape.num_elements().unwrap_or(0) as u32,
915 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
916 data_elem_bytes: data_shape.dtype().size_bytes() as u8,
917 axis: *axis,
918 }
919}
920
921#[allow(unused_variables)]
922pub(crate) fn compile_compare(
923 node: &rlx_ir::Node,
924 graph: &Graph,
925 arena: &crate::arena::Arena,
926 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
927 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
928 rng: rlx_ir::RngOptions,
929) -> Thunk {
930 let Op::Compare(cmp) = &node.op else {
931 unreachable!()
932 };
933 {
934 let len = node.shape.num_elements().unwrap();
935 let lhs_n = graph.node(node.inputs[0]).shape.num_elements().unwrap();
936 let rhs_n = graph.node(node.inputs[1]).shape.num_elements().unwrap();
937 let lhs_scalar = lhs_n == 1 && len > 1;
938 let rhs_scalar = rhs_n == 1 && len > 1;
939 let in_dtype = graph.node(node.inputs[0]).shape.dtype();
940 let inputs_i64 = u8::from(in_dtype == rlx_ir::DType::I64);
941 let lhs_partial = lhs_n != len && lhs_n != 1;
949 let rhs_partial = rhs_n != len && rhs_n != 1;
950 let out_dims_s = get_static_dims(graph, node.id);
951 let lhs_dims_s = get_static_dims(graph, node.inputs[0]);
952 let rhs_dims_s = get_static_dims(graph, node.inputs[1]);
953 let (out_dims_bcast, bcast_lhs_strides, bcast_rhs_strides) = if (lhs_partial || rhs_partial)
954 && !out_dims_s.is_empty()
955 && !lhs_dims_s.is_empty()
956 && !rhs_dims_s.is_empty()
957 {
958 assert!(
962 lhs_dims_s.len() <= out_dims_s.len() && rhs_dims_s.len() <= out_dims_s.len(),
963 "Compare {:?} at {}: operand rank exceeds output rank \
964 (lhs {lhs_dims_s:?}, rhs {rhs_dims_s:?}, out {out_dims_s:?}) — \
965 broadcasting cannot reduce rank, so the graph's shape metadata is inconsistent",
966 cmp,
967 node.name.as_deref().unwrap_or("<unnamed>"),
968 );
969 (
970 out_dims_s.iter().map(|&d| d as u32).collect::<Vec<u32>>(),
971 broadcast_strides(&lhs_dims_s, &out_dims_s),
972 broadcast_strides(&rhs_dims_s, &out_dims_s),
973 )
974 } else {
975 (Vec::new(), Vec::new(), Vec::new())
976 };
977 Thunk::Compare {
978 lhs: node_offset(arena, node.inputs[0]),
979 rhs: node_offset(arena, node.inputs[1]),
980 dst: node_offset(arena, node.id),
981 len: len as u32,
982 op: *cmp,
983 inputs_i64,
984 inputs_elem_bytes: in_dtype.size_bytes() as u8,
985 dst_elem_bytes: node.shape.dtype().size_bytes() as u8,
986 lhs_scalar,
987 rhs_scalar,
988 lhs_len: lhs_n as u32,
989 rhs_len: rhs_n as u32,
990 out_dims_bcast,
991 bcast_lhs_strides,
992 bcast_rhs_strides,
993 }
994 }
995}
996
997#[allow(unused_variables)]
998pub(crate) fn compile_where(
999 node: &rlx_ir::Node,
1000 graph: &Graph,
1001 arena: &crate::arena::Arena,
1002 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1003 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1004 rng: rlx_ir::RngOptions,
1005) -> Thunk {
1006 let Op::Where = &node.op else { unreachable!() };
1007 {
1008 let len = node.shape.num_elements().unwrap();
1009 let cn = graph.node(node.inputs[0]).shape.num_elements().unwrap();
1010 let tn = graph.node(node.inputs[1]).shape.num_elements().unwrap();
1011 let fn_ = graph.node(node.inputs[2]).shape.num_elements().unwrap();
1012 let cond_scalar = cn == 1 && len > 1;
1013 let true_scalar = tn == 1 && len > 1;
1014 let false_scalar = fn_ == 1 && len > 1;
1015 let elem_bytes = node.shape.dtype().size_bytes() as u8;
1016 let cond_elem_bytes = graph.node(node.inputs[0]).shape.dtype().size_bytes() as u8;
1017 Thunk::Where {
1018 cond: node_offset(arena, node.inputs[0]),
1019 on_true: node_offset(arena, node.inputs[1]),
1020 on_false: node_offset(arena, node.inputs[2]),
1021 dst: node_offset(arena, node.id),
1022 len: len as u32,
1023 elem_bytes,
1024 cond_elem_bytes,
1025 cond_scalar,
1026 true_scalar,
1027 false_scalar,
1028 }
1029 }
1030}
1031
1032#[allow(unused_variables)]
1033pub(crate) fn compile_fma(
1034 node: &rlx_ir::Node,
1035 graph: &Graph,
1036 arena: &crate::arena::Arena,
1037 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1038 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1039 rng: rlx_ir::RngOptions,
1040) -> Thunk {
1041 let Op::Fma = &node.op else { unreachable!() };
1042 {
1043 let len = node.shape.num_elements().unwrap();
1044 Thunk::Fma {
1045 a: node_offset(arena, node.inputs[0]),
1046 b: node_offset(arena, node.inputs[1]),
1047 c: node_offset(arena, node.inputs[2]),
1048 dst: node_offset(arena, node.id),
1049 len: len as u32,
1050 elem_bytes: node.shape.dtype().size_bytes() as u8,
1051 }
1052 }
1053}
1054
1055#[allow(unused_variables)]
1056pub(crate) fn compile_relu_backward(
1057 node: &rlx_ir::Node,
1058 graph: &Graph,
1059 arena: &crate::arena::Arena,
1060 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1061 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1062 rng: rlx_ir::RngOptions,
1063) -> Thunk {
1064 let Op::ReluBackward = &node.op else {
1065 unreachable!()
1066 };
1067 {
1068 let len: usize = (0..node.shape.rank())
1069 .map(|i| node.shape.dim(i).unwrap_static())
1070 .product();
1071 let x = node_offset(arena, node.inputs[0]);
1072 let dy = node_offset(arena, node.inputs[1]);
1073 let dx = node_offset(arena, node.id);
1074 match node.shape.dtype() {
1075 rlx_ir::DType::F64 => Thunk::ReluBackwardF64 {
1076 x,
1077 dy,
1078 dx,
1079 len: len as u32,
1080 },
1081 _ => Thunk::ReluBackward {
1082 x,
1083 dy,
1084 dx,
1085 len: len as u32,
1086 },
1087 }
1088 }
1089}
1090
1091#[allow(unused_variables)]
1092pub(crate) fn compile_activation_backward(
1093 node: &rlx_ir::Node,
1094 graph: &Graph,
1095 arena: &crate::arena::Arena,
1096 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1097 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1098 rng: rlx_ir::RngOptions,
1099) -> Thunk {
1100 let Op::ActivationBackward { kind } = &node.op else {
1101 unreachable!()
1102 };
1103 {
1104 let len: usize = (0..node.shape.rank())
1105 .map(|i| node.shape.dim(i).unwrap_static())
1106 .product();
1107 let x = node_offset(arena, node.inputs[0]);
1108 let dy = node_offset(arena, node.inputs[1]);
1109 let dx = node_offset(arena, node.id);
1110 match node.shape.dtype() {
1111 rlx_ir::DType::F64 => Thunk::ActivationBackwardF64 {
1112 x,
1113 dy,
1114 dx,
1115 len: len as u32,
1116 kind: *kind,
1117 },
1118 _ => Thunk::ActivationBackward {
1119 x,
1120 dy,
1121 dx,
1122 len: len as u32,
1123 kind: *kind,
1124 },
1125 }
1126 }
1127}
1128
1129#[allow(unused_variables)]
1130pub(crate) fn compile_gather_backward(
1131 node: &rlx_ir::Node,
1132 graph: &Graph,
1133 arena: &crate::arena::Arena,
1134 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1135 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1136 rng: rlx_ir::RngOptions,
1137) -> Thunk {
1138 let Op::GatherBackward { .. } = &node.op else {
1139 unreachable!()
1140 };
1141 {
1142 let dy_shape = &graph.node(node.inputs[0]).shape;
1143 let idx_shape = &graph.node(node.inputs[1]).shape;
1144 let out_shape = &node.shape;
1145 let rank = out_shape.rank();
1146 let axis = match &node.op {
1147 Op::GatherBackward { axis } => *axis,
1148 _ => 0,
1149 };
1150 let axis_u = if axis < 0 {
1151 (rank as i32 + axis) as usize
1152 } else {
1153 axis as usize
1154 };
1155 let outer: usize = (0..axis_u)
1156 .map(|i| dy_shape.dim(i).unwrap_static())
1157 .product::<usize>()
1158 .max(1);
1159 let num_idx = idx_shape.gather_index_count(axis_u);
1160 let trailing: usize = (axis_u + 1..dy_shape.rank())
1161 .map(|i| dy_shape.dim(i).unwrap_static())
1162 .product::<usize>()
1163 .max(1);
1164 let axis_dim = out_shape.dim(axis_u).unwrap_static();
1165 Thunk::GatherBackward {
1166 dy: node_offset(arena, node.inputs[0]),
1167 indices: node_offset(arena, node.inputs[1]),
1168 dst: node_offset(arena, node.id),
1169 outer: outer as u32,
1170 axis_dim: axis_dim as u32,
1171 num_idx: num_idx as u32,
1172 trailing: trailing as u32,
1173 }
1174 }
1175}
1176
1177#[allow(unused_variables)]
1178pub(crate) fn compile_concat(
1179 node: &rlx_ir::Node,
1180 graph: &Graph,
1181 arena: &crate::arena::Arena,
1182 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1183 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1184 rng: rlx_ir::RngOptions,
1185) -> Thunk {
1186 let Op::Concat { axis } = &node.op else {
1187 unreachable!()
1188 };
1189 {
1190 let out_shape = &node.shape;
1194 let rank = out_shape.rank();
1195 let outer: usize = (0..*axis)
1196 .map(|i| out_shape.dim(i).unwrap_static())
1197 .product::<usize>()
1198 .max(1);
1199 let inner: usize = (*axis + 1..rank)
1200 .map(|i| out_shape.dim(i).unwrap_static())
1201 .product::<usize>()
1202 .max(1);
1203 let total_axis = out_shape.dim(*axis).unwrap_static();
1204 let inputs: Vec<(usize, u32, u32)> = node
1205 .inputs
1206 .iter()
1207 .map(|&in_id| {
1208 let in_shape = &graph.node(in_id).shape;
1209 let in_axis = concat_axis_extent(in_shape, *axis, rank);
1210 let in_numel = in_shape.num_elements().unwrap_or(0) as u32;
1211 (node_offset(arena, in_id), in_axis as u32, in_numel)
1212 })
1213 .collect();
1214 let dst = node_offset(arena, node.id);
1215 match out_shape.dtype().size_bytes() {
1223 8 => Thunk::ConcatF64 {
1224 dst,
1225 outer: outer as u32,
1226 inner: inner as u32,
1227 total_axis: total_axis as u32,
1228 inputs,
1229 },
1230 _ => Thunk::Concat {
1231 dst,
1232 outer: outer as u32,
1233 inner: inner as u32,
1234 total_axis: total_axis as u32,
1235 inputs,
1236 },
1237 }
1238 }
1239}
1240
1241#[allow(unused_variables)]
1242pub(crate) fn compile_elementwise_region(
1243 node: &rlx_ir::Node,
1244 graph: &Graph,
1245 arena: &crate::arena::Arena,
1246 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1247 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1248 rng: rlx_ir::RngOptions,
1249) -> Thunk {
1250 let Op::ElementwiseRegion {
1251 chain,
1252 scalar_input_mask,
1253 input_modulus,
1254 prologue,
1255 ..
1256 } = &node.op
1257 else {
1258 unreachable!()
1259 };
1260 {
1261 if *prologue != rlx_ir::op::RegionPrologue::None {
1265 Thunk::Nop
1266 } else {
1267 let input_offs: Vec<usize> = node
1268 .inputs
1269 .iter()
1270 .map(|&id| node_offset(arena, id))
1271 .collect();
1272 let lane_of = |id: NodeId| {
1277 let n = graph.node(id).shape.num_elements().unwrap_or(0);
1278 let bytes = arena.byte_size(id);
1279 let elem = bytes.checked_div(n).unwrap_or(4);
1280 crate::thunk::types::LaneKind::resolve(graph.node(id).shape.dtype(), elem)
1281 };
1282 let in_lanes: Vec<crate::thunk::types::LaneKind> =
1283 node.inputs.iter().map(|&id| lane_of(id)).collect();
1284 Thunk::ElementwiseRegion {
1285 dst: node_offset(arena, node.id),
1286 len: node.shape.num_elements().unwrap_or(0) as u32,
1287 input_offs,
1288 chain: chain.clone(),
1289 scalar_input_mask: *scalar_input_mask,
1290 input_modulus: *input_modulus,
1291 in_lanes,
1292 out_lane: lane_of(node.id),
1293 }
1294 }
1295 }
1296}
1297
1298pub(crate) fn get_len(graph: &Graph, id: NodeId) -> usize {
1299 graph.node(id).shape.num_elements().unwrap_or(0)
1300}
1301
1302pub(crate) fn get_static_dims(graph: &Graph, id: NodeId) -> Vec<usize> {
1304 let dims = graph.node(id).shape.dims();
1305 let mut out = Vec::with_capacity(dims.len());
1306 for d in dims {
1307 if let Some(s) = match d {
1308 rlx_ir::Dim::Static(s) => Some(*s),
1309 _ => None,
1310 } {
1311 out.push(s);
1312 } else {
1313 return Vec::new();
1314 }
1315 }
1316 out
1317}
1318
1319pub(crate) fn concat_axis_extent(input: &rlx_ir::Shape, axis: usize, out_rank: usize) -> usize {
1322 let in_rank = input.rank();
1323 if axis >= out_rank {
1324 return 1;
1325 }
1326 if axis < in_rank {
1327 input.dim(axis).unwrap_static()
1328 } else {
1329 1
1330 }
1331}
1332
1333pub(crate) fn broadcast_src_index(src_idx: usize, in_len: usize) -> usize {
1334 if in_len == 0 { 0 } else { src_idx % in_len }
1335}
1336
1337macro_rules! concat_copy_rows {
1341 ($name:ident, $t:ty) => {
1342 pub(crate) fn $name(
1343 out: &mut [$t],
1344 inp: &[$t],
1345 outer: usize,
1346 copy_per_row: usize,
1347 row_stride: usize,
1348 dst_col_off: usize,
1349 in_numel: usize,
1350 ) {
1351 let need = outer.saturating_mul(copy_per_row.max(1));
1352 let broadcast_outer = in_numel < need;
1353 let out_ptr = out.as_mut_ptr() as usize;
1354 let inp_ptr = inp.as_ptr() as usize;
1355 let inp_len = inp.len();
1356 let run = |o0: usize, o1: usize| {
1357 let out = unsafe { std::slice::from_raw_parts_mut(out_ptr as *mut $t, out.len()) };
1358 let inp = unsafe { std::slice::from_raw_parts(inp_ptr as *const $t, inp_len) };
1359 for o in o0..o1 {
1360 let dst_row_start = o * row_stride + dst_col_off;
1361 if broadcast_outer {
1362 if in_numel == 1 {
1363 if copy_per_row == 1 {
1364 out[dst_row_start] = inp[0];
1365 } else {
1366 out[dst_row_start..dst_row_start + copy_per_row].fill(inp[0]);
1367 }
1368 } else if copy_per_row <= inp.len() {
1369 out[dst_row_start..dst_row_start + copy_per_row]
1370 .copy_from_slice(&inp[..copy_per_row]);
1371 } else if !inp.is_empty() {
1372 out[dst_row_start..dst_row_start + copy_per_row].fill(inp[0]);
1373 }
1374 } else {
1375 let src_row_start = o * copy_per_row;
1376 out[dst_row_start..dst_row_start + copy_per_row]
1377 .copy_from_slice(&inp[src_row_start..src_row_start + copy_per_row]);
1378 }
1379 }
1380 };
1381 if outer >= 8
1383 && copy_per_row >= 16
1384 && crate::pool::num_threads() > 1
1385 && crate::pool::should_parallelize(outer.saturating_mul(copy_per_row))
1386 {
1387 crate::pool::par_for(outer, 1, &|off, cnt| run(off, off + cnt));
1388 } else {
1389 run(0, outer);
1390 }
1391 }
1392 };
1393}
1394
1395concat_copy_rows!(concat_copy_rows_f32, f32);
1396concat_copy_rows!(concat_copy_rows_f64, f64);
1397
1398pub(crate) fn is_trailing_bias_broadcast(
1416 rhs_dims: &[rlx_ir::Dim],
1417 out_dims: &[rlx_ir::Dim],
1418) -> bool {
1419 if rhs_dims.len() > out_dims.len() {
1420 return false;
1421 }
1422 let off = out_dims.len() - rhs_dims.len();
1423 for i in 0..rhs_dims.len() {
1424 let r = match rhs_dims[i] {
1425 rlx_ir::Dim::Static(n) => n,
1426 _ => return false,
1427 };
1428 let o = match out_dims[off + i] {
1429 rlx_ir::Dim::Static(n) => n,
1430 _ => return false,
1431 };
1432 if r != o {
1433 return false;
1434 }
1435 }
1436 true
1437}
1438
1439pub(crate) fn broadcast_strides(in_dims: &[usize], out_dims: &[usize]) -> Vec<u32> {
1440 let r_out = out_dims.len();
1441 let r_in = in_dims.len();
1442 assert!(
1443 r_in <= r_out,
1444 "broadcast: input rank {r_in} > output rank {r_out}"
1445 );
1446 let pad = r_out - r_in;
1447 let mut strides = vec![0u32; r_out];
1448 let mut acc: usize = 1;
1449 for d in (0..r_out).rev() {
1450 let in_size = if d < pad { 1 } else { in_dims[d - pad] };
1451 if in_size == 1 {
1452 strides[d] = 0;
1453 } else {
1454 assert_eq!(
1455 in_size, out_dims[d],
1456 "broadcast: input dim {in_size} doesn't match output dim {} at axis {d}",
1457 out_dims[d]
1458 );
1459 strides[d] = acc as u32;
1460 acc *= in_size;
1461 }
1462 }
1463 strides
1464}
1465
1466#[inline(always)]
1467pub(crate) fn exec_elementwise_region(t: &Thunk, base: *mut u8) {
1468 let Thunk::ElementwiseRegion {
1469 dst,
1470 len,
1471 input_offs,
1472 chain,
1473 scalar_input_mask,
1474 input_modulus,
1475 in_lanes,
1476 out_lane,
1477 } = t
1478 else {
1479 unreachable!()
1480 };
1481 {
1482 let len = *len as usize;
1483 if !chain.is_empty() && len > 0 {
1484 let base_addr = base as usize;
1485 let dst = *dst;
1486 let scalar_mask = *scalar_input_mask;
1487 let out_lane = *out_lane;
1489 let eval = |gid: usize| {
1490 let v = region_eval_elem(
1491 gid,
1492 base_addr as *const u8,
1493 input_offs,
1494 in_lanes,
1495 chain,
1496 scalar_mask,
1497 input_modulus,
1498 );
1499 unsafe {
1502 out_lane.store(base_addr as *mut u8, dst, gid, v);
1503 }
1504 };
1505 if fast_conv_enabled() && crate::pool::should_parallelize(len) {
1506 crate::pool::par_for(len, crate::pool::chunk_floor(len), &|off, cnt| {
1507 for gid in off..off + cnt {
1508 eval(gid);
1509 }
1510 });
1511 } else {
1512 for gid in 0..len {
1513 eval(gid);
1514 }
1515 }
1516 }
1517 }
1518}
1519
1520#[inline(always)]
1521pub(crate) fn exec_transpose_f64(t: &Thunk, base: *mut u8) {
1522 let Thunk::TransposeF64 {
1523 src,
1524 dst,
1525 in_total,
1526 out_dims,
1527 in_strides,
1528 } = t
1529 else {
1530 unreachable!()
1531 };
1532 unsafe {
1533 let inp = sl_f64(*src, base, *in_total as usize);
1534 let out_total: usize = out_dims.iter().map(|d| *d as usize).product();
1535 let out = sl_mut_f64(*dst, base, out_total);
1536 transpose_walk_f64(inp, out, out_dims, in_strides);
1537 }
1538}
1539
1540#[inline(always)]
1541pub(crate) fn exec_activation_f64(t: &Thunk, base: *mut u8) {
1542 let Thunk::ActivationF64 {
1543 src,
1544 dst,
1545 len,
1546 kind,
1547 } = t
1548 else {
1549 unreachable!()
1550 };
1551 {
1552 let len = *len as usize;
1553 unsafe {
1554 let inp = sl_f64(*src, base, len);
1555 let out = sl_mut_f64(*dst, base, len);
1556 apply_activation_f64(inp, out, *kind);
1557 }
1558 }
1559}
1560
1561#[inline(always)]
1562pub(crate) fn exec_binary_full_f64(t: &Thunk, base: *mut u8) {
1563 let Thunk::BinaryFullF64 {
1564 lhs,
1565 rhs,
1566 dst,
1567 len,
1568 lhs_len,
1569 rhs_len,
1570 op,
1571 out_dims_bcast,
1572 bcast_lhs_strides,
1573 bcast_rhs_strides,
1574 } = t
1575 else {
1576 unreachable!()
1577 };
1578 {
1579 let len = *len as usize;
1580 let lhs_len = *lhs_len as usize;
1581 let rhs_len = *rhs_len as usize;
1582 unsafe {
1583 let l = sl_f64(*lhs, base, lhs_len);
1584 let r = sl_f64(*rhs, base, rhs_len);
1585 let d = sl_mut_f64(*dst, base, len);
1586 if lhs_len == len && rhs_len == len {
1587 for i in 0..len {
1588 d[i] = binary_op_f64(*op, l[i], r[i]);
1589 }
1590 } else if !out_dims_bcast.is_empty() {
1591 let rank = out_dims_bcast.len();
1595 let mut coords = vec![0u32; rank];
1596 for i in 0..len {
1597 let mut rem = i;
1598 for ax in (0..rank).rev() {
1599 let sz = out_dims_bcast[ax] as usize;
1600 coords[ax] = (rem % sz) as u32;
1601 rem /= sz;
1602 }
1603 let mut li: usize = 0;
1604 let mut ri: usize = 0;
1605 for ax in 0..rank {
1606 li += coords[ax] as usize * bcast_lhs_strides[ax] as usize;
1607 ri += coords[ax] as usize * bcast_rhs_strides[ax] as usize;
1608 }
1609 d[i] = binary_op_f64(*op, l[li], r[ri]);
1610 }
1611 } else {
1612 for i in 0..len {
1617 d[i] = binary_op_f64(*op, l[i % lhs_len], r[i % rhs_len]);
1618 }
1619 }
1620 }
1621 }
1622}
1623
1624#[inline(always)]
1625pub(crate) fn exec_binary_full_c64(t: &Thunk, base: *mut u8) {
1626 let Thunk::BinaryFullC64 {
1627 lhs,
1628 rhs,
1629 dst,
1630 len,
1631 lhs_len,
1632 rhs_len,
1633 op,
1634 out_dims_bcast,
1635 bcast_lhs_strides,
1636 bcast_rhs_strides,
1637 } = t
1638 else {
1639 unreachable!()
1640 };
1641 {
1642 let n_out = *len as usize;
1648 let n_l = *lhs_len as usize;
1649 let n_r = *rhs_len as usize;
1650 unsafe {
1651 let l = sl(*lhs, base, 2 * n_l);
1652 let r = sl(*rhs, base, 2 * n_r);
1653 let d = sl_mut(*dst, base, 2 * n_out);
1654 let do_c64 = |a_re: f32, a_im: f32, b_re: f32, b_im: f32| -> (f32, f32) {
1655 match op {
1656 BinaryOp::Add => (a_re + b_re, a_im + b_im),
1657 BinaryOp::Sub => (a_re - b_re, a_im - b_im),
1658 BinaryOp::Mul => (a_re * b_re - a_im * b_im, a_re * b_im + a_im * b_re),
1659 BinaryOp::Div => {
1660 let denom = b_re * b_re + b_im * b_im;
1661 (
1662 (a_re * b_re + a_im * b_im) / denom,
1663 (a_im * b_re - a_re * b_im) / denom,
1664 )
1665 }
1666 BinaryOp::Max
1667 | BinaryOp::Min
1668 | BinaryOp::Pow
1669 | BinaryOp::Mod
1670 | BinaryOp::BitAnd
1671 | BinaryOp::BitOr
1672 | BinaryOp::BitXor
1673 | BinaryOp::Shl
1674 | BinaryOp::Shr
1675 | BinaryOp::Atan2 => {
1676 unreachable!("C64 max/min/pow/mod/bitwise/atan2 rejected at lowering")
1677 }
1678 }
1679 };
1680 if n_l == n_out && n_r == n_out {
1681 for i in 0..n_out {
1682 let (re, im) = do_c64(l[2 * i], l[2 * i + 1], r[2 * i], r[2 * i + 1]);
1683 d[2 * i] = re;
1684 d[2 * i + 1] = im;
1685 }
1686 } else if !out_dims_bcast.is_empty() {
1687 let rank = out_dims_bcast.len();
1691 let mut coords = vec![0u32; rank];
1692 for i in 0..n_out {
1693 let mut rem = i;
1694 for ax in (0..rank).rev() {
1695 let sz = out_dims_bcast[ax] as usize;
1696 coords[ax] = (rem % sz) as u32;
1697 rem /= sz;
1698 }
1699 let mut li: usize = 0;
1700 let mut ri: usize = 0;
1701 for ax in 0..rank {
1702 li += coords[ax] as usize * bcast_lhs_strides[ax] as usize;
1703 ri += coords[ax] as usize * bcast_rhs_strides[ax] as usize;
1704 }
1705 let (re, im) = do_c64(l[2 * li], l[2 * li + 1], r[2 * ri], r[2 * ri + 1]);
1706 d[2 * i] = re;
1707 d[2 * i + 1] = im;
1708 }
1709 } else {
1710 for i in 0..n_out {
1712 let li = if n_l == 1 { 0 } else { i % n_l };
1713 let ri = if n_r == 1 { 0 } else { i % n_r };
1714 let (re, im) = do_c64(l[2 * li], l[2 * li + 1], r[2 * ri], r[2 * ri + 1]);
1715 d[2 * i] = re;
1716 d[2 * i + 1] = im;
1717 }
1718 }
1719 }
1720 }
1721}
1722
1723#[inline(always)]
1724pub(crate) fn exec_activation_c64(t: &Thunk, base: *mut u8) {
1725 let Thunk::ActivationC64 {
1726 src,
1727 dst,
1728 len,
1729 kind,
1730 } = t
1731 else {
1732 unreachable!()
1733 };
1734 {
1735 let n = *len as usize;
1736 unsafe {
1737 let s = sl(*src, base, 2 * n);
1738 let d = sl_mut(*dst, base, 2 * n);
1739 for i in 0..n {
1740 let a = s[2 * i];
1741 let b = s[2 * i + 1];
1742 let (re, im) = match kind {
1743 Activation::Neg => (-a, -b),
1744 Activation::Exp => {
1745 let ea = a.exp();
1747 (ea * b.cos(), ea * b.sin())
1748 }
1749 Activation::Log => {
1750 let r = (a * a + b * b).sqrt();
1752 (r.ln(), b.atan2(a))
1753 }
1754 Activation::Sqrt => {
1755 let r = (a * a + b * b).sqrt();
1758 let re = ((r + a) * 0.5).max(0.0).sqrt();
1759 let im_mag = ((r - a) * 0.5).max(0.0).sqrt();
1760 let im = if b >= 0.0 { im_mag } else { -im_mag };
1761 (re, im)
1762 }
1763 _ => unreachable!("non-C64 activation kind survived lowering"),
1764 };
1765 d[2 * i] = re;
1766 d[2 * i + 1] = im;
1767 }
1768 }
1769 }
1770}
1771
1772#[inline(always)]
1773pub(crate) fn exec_gather(t: &Thunk, base: *mut u8) {
1774 let Thunk::Gather {
1775 table,
1776 table_len,
1777 idx,
1778 dst,
1779 num_idx,
1780 trailing,
1781 idx_i64,
1782 table_bytes,
1783 } = t
1784 else {
1785 unreachable!()
1786 };
1787 {
1788 let (ni, tr) = (*num_idx as usize, *trailing as usize);
1789 let rows = *table_len as usize / tr.max(1);
1790 unsafe {
1791 if *table_bytes == 8 {
1792 let tab = sl_i64(*table, base, *table_len as usize);
1793 let out = sl_mut_i64(*dst, base, ni * tr);
1794 if *idx_i64 != 0 {
1795 let ids = sl_i64(*idx, base, ni);
1796 for i in 0..ni {
1797 let row = ids[i].max(0) as usize;
1798 if row < rows {
1799 out[i * tr..(i + 1) * tr]
1800 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1801 }
1802 }
1803 } else {
1804 let ids = sl(*idx, base, ni);
1805 for i in 0..ni {
1806 let row = ids[i] as usize;
1807 if row < rows {
1808 out[i * tr..(i + 1) * tr]
1809 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1810 }
1811 }
1812 }
1813 } else {
1814 let tab = sl(*table, base, *table_len as usize);
1815 let out = sl_mut(*dst, base, ni * tr);
1816 if *idx_i64 != 0 {
1817 let ids = sl_i64(*idx, base, ni);
1818 for i in 0..ni {
1819 let row = ids[i].max(0) as usize;
1820 if row < rows {
1821 out[i * tr..(i + 1) * tr]
1822 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1823 }
1824 }
1825 } else {
1826 let ids = sl(*idx, base, ni);
1827 for i in 0..ni {
1828 let row = ids[i] as usize;
1829 if row < rows {
1830 out[i * tr..(i + 1) * tr]
1831 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1832 }
1833 }
1834 }
1835 }
1836 }
1837 }
1838}
1839
1840#[inline(always)]
1841pub(crate) fn exec_activation_in_place(t: &Thunk, base: *mut u8) {
1842 let Thunk::ActivationInPlace { data, len, act } = t else {
1843 unreachable!()
1844 };
1845 {
1846 let len = *len as usize;
1847 unsafe {
1848 let d = sl_mut(*data, base, len);
1849 let par = crate::pool::should_parallelize(len);
1855 macro_rules! apply {
1856 ($f:expr) => {{
1857 let f = $f;
1858 if par {
1859 use rayon::prelude::*;
1860 d.par_iter_mut().for_each(|v| *v = f(*v));
1861 } else {
1862 for v in d.iter_mut() {
1863 *v = f(*v);
1864 }
1865 }
1866 }};
1867 }
1868 match act {
1869 Activation::Gelu => crate::kernels::par_gelu_inplace(d),
1870 Activation::GeluApprox => crate::kernels::par_gelu_approx_inplace(d),
1871 Activation::Silu => crate::kernels::par_silu_inplace(d),
1872 Activation::Relu => apply!(|x: f32| x.max(0.0)),
1873 Activation::Sigmoid => {
1878 apply!(|x: f32| 1.0 / (1.0 + crate::vmath::exp_poly(-x)))
1879 }
1880 Activation::Tanh => crate::vmath::vvtanhf_hot_inplace(d),
1881 Activation::Exp => crate::vmath::vvexpf_hot_inplace(d),
1882 Activation::Log => crate::vmath::vvlogf_inplace(d),
1883 Activation::Sqrt => crate::vmath::vvsqrtf_inplace(d),
1884 Activation::Rsqrt => crate::vmath::vvrsqrtf_inplace(d),
1885 Activation::Neg => apply!(|x: f32| -x),
1886 Activation::Abs => apply!(|x: f32| x.abs()),
1887 Activation::Round => apply!(|x: f32| x.round()),
1888 Activation::Sin => crate::vmath::vvsinf_fast_inplace(d),
1892 Activation::Cos => crate::vmath::vvcosf_fast_inplace(d),
1893 Activation::Tan => crate::vmath::vvtanf_fast_inplace(d),
1894 Activation::Atan => crate::vmath::vvatanf_fast_inplace(d),
1895 Activation::Recip => crate::vmath::vvrecf_inplace(d),
1896 Activation::Floor => apply!(|x: f32| x.floor()),
1897 Activation::Ceil => apply!(|x: f32| x.ceil()),
1898 Activation::Sign => {
1899 apply!(|x: f32| if x > 0.0 {
1900 1.0
1901 } else if x < 0.0 {
1902 -1.0
1903 } else {
1904 0.0
1905 })
1906 }
1907 Activation::Softplus => apply!(crate::vmath::softplus_poly),
1908 Activation::Elu => {
1909 apply!(|x: f32| if x > 0.0 {
1910 x
1911 } else {
1912 crate::vmath::exp_poly(x) - 1.0
1913 })
1914 }
1915 Activation::Erf => apply!(|x: f32| erf_f32(x)),
1916 Activation::HardSwish => apply!(|x: f32| x * (x + 3.0).clamp(0.0, 6.0) / 6.0),
1917 Activation::HardSigmoid => apply!(|x: f32| (x / 6.0 + 0.5).clamp(0.0, 1.0)),
1918 Activation::Mish => {
1919 apply!(|x: f32| x * crate::vmath::tanh_poly(crate::vmath::softplus_poly(x)))
1920 }
1921 Activation::Softsign => apply!(|x: f32| x / (1.0 + x.abs())),
1922 Activation::LogSigmoid => {
1923 apply!(|x: f32| x.min(0.0)
1924 - crate::vmath::ln_1p_poly(crate::vmath::exp_poly(-x.abs())))
1925 }
1926 }
1927 }
1928 }
1929}
1930
1931#[inline(always)]
1932pub(crate) fn exec_kv_append(t: &Thunk, base: *mut u8) {
1938 let Thunk::KvAppend {
1939 src,
1940 dst,
1941 outer,
1942 seq_cap,
1943 pos,
1944 inner_bytes,
1945 } = t
1946 else {
1947 unreachable!()
1948 };
1949 let (outer, seq_cap, pos, inner_bytes) = (
1950 *outer as usize,
1951 *seq_cap as usize,
1952 *pos as usize,
1953 *inner_bytes as usize,
1954 );
1955 if inner_bytes == 0 || outer == 0 || pos >= seq_cap {
1956 return;
1960 }
1961 unsafe {
1962 let row = base.add(*src);
1963 let cache = base.add(*dst);
1964 if outer == 1 {
1965 std::ptr::copy_nonoverlapping(row, cache.add(pos * inner_bytes), inner_bytes);
1967 } else {
1968 let stride = seq_cap * inner_bytes;
1969 for o in 0..outer {
1970 std::ptr::copy_nonoverlapping(
1971 row.add(o * inner_bytes),
1972 cache.add(o * stride + pos * inner_bytes),
1973 inner_bytes,
1974 );
1975 }
1976 }
1977 }
1978}
1979
1980pub(crate) fn exec_concat(t: &Thunk, base: *mut u8) {
1981 let Thunk::Concat {
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(*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 numel = (*in_numel as usize).max(1);
2005 let d0 = *dst;
2019 let d1 = d0 + out_total * 4;
2020 let s0 = *src_off;
2021 let s1 = s0 + numel * 4;
2022 let staged: Option<Vec<f32>> =
2023 (s0 < d1 && d0 < s1).then(|| sl(*src_off, base, numel).to_vec());
2024 let inp = match &staged {
2025 Some(v) => v.as_slice(),
2026 None => sl(*src_off, base, numel),
2027 };
2028 concat_copy_rows_f32(
2029 out,
2030 inp,
2031 outer,
2032 copy_per_row,
2033 row_stride,
2034 dst_col_off,
2035 *in_numel as usize,
2036 );
2037 cum += in_axis;
2038 }
2039 }
2040 }
2041}
2042
2043#[inline(always)]
2044pub(crate) fn exec_concat_f64(t: &Thunk, base: *mut u8) {
2045 let Thunk::ConcatF64 {
2046 dst,
2047 outer,
2048 inner,
2049 total_axis,
2050 inputs,
2051 } = t
2052 else {
2053 unreachable!()
2054 };
2055 {
2056 let outer = *outer as usize;
2057 let inner = *inner as usize;
2058 let total_axis = *total_axis as usize;
2059 let row_stride = total_axis * inner;
2060 let out_total = outer * row_stride;
2061 unsafe {
2062 let out = sl_mut_f64(*dst, base, out_total);
2063 let mut cum: usize = 0;
2064 for (src_off, in_axis, in_numel) in inputs {
2065 let in_axis = *in_axis as usize;
2066 let copy_per_row = in_axis * inner;
2067 let dst_col_off = cum * inner;
2068 let inp = sl_f64(*src_off, base, (*in_numel as usize).max(1));
2069 concat_copy_rows_f64(
2070 out,
2071 inp,
2072 outer,
2073 copy_per_row,
2074 row_stride,
2075 dst_col_off,
2076 *in_numel as usize,
2077 );
2078 cum += in_axis;
2079 }
2080 }
2081 }
2082}
2083
2084#[inline(always)]
2085pub(crate) fn exec_scatter_add(t: &Thunk, base: *mut u8) {
2086 let Thunk::ScatterAdd {
2087 updates,
2088 indices,
2089 dst,
2090 num_updates,
2091 out_dim,
2092 trailing,
2093 } = t
2094 else {
2095 unreachable!()
2096 };
2097 {
2098 let num_updates = *num_updates as usize;
2099 let out_dim = *out_dim as usize;
2100 let trailing = *trailing as usize;
2101 unsafe {
2102 let upd = sl(*updates, base, num_updates * trailing);
2103 let ids = sl(*indices, base, num_updates);
2104 let out = sl_mut(*dst, base, out_dim * trailing);
2105 for v in out.iter_mut() {
2107 *v = 0.0;
2108 }
2109 for i in 0..num_updates {
2110 let row = ids[i] as usize;
2111 debug_assert!(row < out_dim, "ScatterAdd index out of range");
2112 let src_off = i * trailing;
2113 let dst_off = row * trailing;
2114 for j in 0..trailing {
2115 out[dst_off + j] += upd[src_off + j];
2116 }
2117 }
2118 }
2119 }
2120}
2121
2122#[inline(always)]
2123pub(crate) fn exec_scatter_nd(t: &Thunk, base: *mut u8) {
2124 let Thunk::ScatterNd {
2125 data,
2126 indices,
2127 updates,
2128 dst,
2129 data_shape,
2130 indices_shape,
2131 data_len,
2132 updates_len,
2133 indices_len,
2134 indices_i64,
2135 reduction,
2136 } = t
2137 else {
2138 unreachable!()
2139 };
2140 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2141 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2142 let data_len = *data_len as usize;
2143 let updates_len = *updates_len as usize;
2144 let indices_len = *indices_len as usize;
2145 unsafe {
2146 let data_s = sl(*data, base, data_len);
2147 let updates_s = sl(*updates, base, updates_len);
2148 let out = sl_mut(*dst, base, data_len);
2149 let idx_i64: Vec<i64> = if *indices_i64 != 0 {
2150 let ptr = base.add(*indices) as *const i64;
2151 std::slice::from_raw_parts(ptr, indices_len).to_vec()
2152 } else {
2153 sl(*indices, base, indices_len)
2154 .iter()
2155 .map(|&x| x as i64)
2156 .collect()
2157 };
2158 let (offsets, slice) =
2159 crate::onnx_indexing::scatter_nd_dst_offsets(&data_shape, &idx_i64, &indices_shape);
2160 crate::onnx_indexing::scatter_nd_into_f32(
2161 data_s, updates_s, out, &offsets, slice, *reduction,
2162 );
2163 }
2164}
2165
2166fn load_indices_i64(base: *mut u8, off: usize, len: usize, as_i64: u8) -> Vec<i64> {
2167 unsafe {
2168 if as_i64 != 0 {
2169 let ptr = base.add(off) as *const i64;
2170 std::slice::from_raw_parts(ptr, len).to_vec()
2171 } else {
2172 sl(off, base, len).iter().map(|&x| x as i64).collect()
2173 }
2174 }
2175}
2176
2177#[inline(always)]
2178pub(crate) fn exec_scatter_elements(t: &Thunk, base: *mut u8) {
2179 let Thunk::ScatterElements {
2180 data,
2181 indices,
2182 updates,
2183 dst,
2184 data_shape,
2185 indices_shape,
2186 data_len,
2187 updates_len,
2188 indices_len,
2189 indices_i64,
2190 axis,
2191 reduction,
2192 } = t
2193 else {
2194 unreachable!()
2195 };
2196 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2197 let idx_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2198 unsafe {
2199 let data_s = sl(*data, base, *data_len as usize);
2200 let updates_s = sl(*updates, base, *updates_len as usize);
2201 let out = sl_mut(*dst, base, *data_len as usize);
2202 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
2203 crate::onnx_indexing::scatter_elements_f32(
2204 data_s,
2205 updates_s,
2206 &idx,
2207 out,
2208 &data_shape,
2209 &idx_shape,
2210 *axis,
2211 *reduction,
2212 );
2213 }
2214}
2215
2216#[inline(always)]
2217pub(crate) fn exec_gather_nd(t: &Thunk, base: *mut u8) {
2218 let Thunk::GatherNd {
2219 data,
2220 indices,
2221 dst,
2222 data_shape,
2223 indices_shape,
2224 data_len,
2225 indices_len,
2226 out_len,
2227 indices_i64,
2228 batch_dims,
2229 } = t
2230 else {
2231 unreachable!()
2232 };
2233 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2234 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2235 unsafe {
2236 let data_s = sl(*data, base, *data_len as usize);
2237 let out = sl_mut(*dst, base, *out_len as usize);
2238 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
2239 let (offsets, slice) = crate::onnx_indexing::gather_nd_src_offsets(
2240 &data_shape,
2241 &idx,
2242 &indices_shape,
2243 (*batch_dims).max(0) as usize,
2244 );
2245 for (t, &off) in offsets.iter().enumerate() {
2246 let d0 = t * slice;
2247 for j in 0..slice {
2248 if let (Some(&s), Some(d)) = (data_s.get(off + j), out.get_mut(d0 + j)) {
2249 *d = s;
2250 }
2251 }
2252 }
2253 }
2254}
2255
2256#[inline(always)]
2257pub(crate) fn exec_gather_elements(t: &Thunk, base: *mut u8) {
2258 let Thunk::GatherElements {
2259 data,
2260 indices,
2261 dst,
2262 data_shape,
2263 indices_shape,
2264 data_len,
2265 indices_len,
2266 out_len,
2267 indices_i64,
2268 data_elem_bytes,
2269 axis,
2270 } = t
2271 else {
2272 unreachable!()
2273 };
2274 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
2275 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
2276 let elem_bytes = (*data_elem_bytes as usize).max(1);
2277 unsafe {
2278 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
2279 if elem_bytes == 4 {
2280 let data_s = sl(*data, base, *data_len as usize);
2282 let out = sl_mut(*dst, base, *out_len as usize);
2283 crate::onnx_indexing::gather_elements_f32(
2284 data_s,
2285 &idx,
2286 out,
2287 &data_shape,
2288 &indices_shape,
2289 *axis,
2290 );
2291 } else {
2292 let data_b =
2294 std::slice::from_raw_parts(base.add(*data), *data_len as usize * elem_bytes);
2295 let out_b =
2296 std::slice::from_raw_parts_mut(base.add(*dst), *out_len as usize * elem_bytes);
2297 crate::onnx_indexing::gather_elements_bytes(
2298 data_b,
2299 &idx,
2300 out_b,
2301 &data_shape,
2302 &indices_shape,
2303 *axis,
2304 elem_bytes,
2305 );
2306 }
2307 }
2308}
2309
2310#[inline(always)]
2311pub(crate) fn exec_relu_backward(t: &Thunk, base: *mut u8) {
2312 let Thunk::ReluBackward { x, dy, dx, len } = t else {
2313 unreachable!()
2314 };
2315 {
2316 let len = *len as usize;
2317 unsafe {
2318 let xs = sl(*x, base, len);
2319 let dys = sl(*dy, base, len);
2320 let out = sl_mut(*dx, base, len);
2321 if fast_conv_enabled() && crate::pool::should_parallelize(len) {
2322 let oa = out.as_mut_ptr() as usize;
2323 crate::pool::par_for(len, crate::pool::chunk_floor(len), &|off, cnt| {
2324 for i in off..off + cnt {
2325 *((oa as *mut f32).add(i)) = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2326 }
2327 });
2328 } else {
2329 for i in 0..len {
2330 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2331 }
2332 }
2333 }
2334 }
2335}
2336
2337#[inline(always)]
2338pub(crate) fn exec_relu_backward_f64(t: &Thunk, base: *mut u8) {
2339 let Thunk::ReluBackwardF64 { x, dy, dx, len } = t else {
2340 unreachable!()
2341 };
2342 {
2343 let len = *len as usize;
2344 unsafe {
2345 let xs = sl_f64(*x, base, len);
2346 let dys = sl_f64(*dy, base, len);
2347 let out = sl_mut_f64(*dx, base, len);
2348 for i in 0..len {
2349 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2350 }
2351 }
2352 }
2353}
2354
2355#[inline(always)]
2356pub(crate) fn exec_activation_backward(t: &Thunk, base: *mut u8) {
2357 let Thunk::ActivationBackward {
2358 x,
2359 dy,
2360 dx,
2361 len,
2362 kind,
2363 } = t
2364 else {
2365 unreachable!()
2366 };
2367 {
2368 let len = *len as usize;
2369 unsafe {
2370 let xs = sl(*x, base, len);
2371 let dys = sl(*dy, base, len);
2372 let out = sl_mut(*dx, base, len);
2373 activation_backward_kernel(*kind, xs, dys, out);
2374 }
2375 }
2376}
2377
2378#[inline(always)]
2379pub(crate) fn exec_activation_backward_f64(t: &Thunk, base: *mut u8) {
2380 let Thunk::ActivationBackwardF64 {
2381 x,
2382 dy,
2383 dx,
2384 len,
2385 kind,
2386 } = t
2387 else {
2388 unreachable!()
2389 };
2390 {
2391 let len = *len as usize;
2392 unsafe {
2393 let xs = sl_f64(*x, base, len);
2394 let dys = sl_f64(*dy, base, len);
2395 let out = sl_mut_f64(*dx, base, len);
2396 activation_backward_kernel_f64(*kind, xs, dys, out);
2397 }
2398 }
2399}
2400
2401#[inline(always)]
2402pub(crate) fn exec_gather_backward(t: &Thunk, base: *mut u8) {
2403 let Thunk::GatherBackward {
2404 dy,
2405 indices,
2406 dst,
2407 outer,
2408 axis_dim,
2409 num_idx,
2410 trailing,
2411 } = t
2412 else {
2413 unreachable!()
2414 };
2415 {
2416 let (outer, axis_dim, num_idx, trailing) = (
2417 *outer as usize,
2418 *axis_dim as usize,
2419 *num_idx as usize,
2420 *trailing as usize,
2421 );
2422 unsafe {
2423 let dys = sl(*dy, base, outer * num_idx * trailing);
2424 let ids = sl(*indices, base, num_idx);
2425 let out = sl_mut(*dst, base, outer * axis_dim * trailing);
2426 for v in out.iter_mut() {
2427 *v = 0.0;
2428 }
2429 crate::training_bwd::gather_axis_backward(
2430 dys, ids, out, outer, axis_dim, num_idx, trailing,
2431 );
2432 }
2433 }
2434}
2435
2436#[inline(always)]
2437pub(crate) fn exec_gather_axis(t: &Thunk, base: *mut u8) {
2438 let Thunk::GatherAxis {
2439 table,
2440 idx,
2441 dst,
2442 outer,
2443 axis_dim,
2444 num_idx,
2445 trailing,
2446 idx_i64,
2447 table_bytes,
2448 } = t
2449 else {
2450 unreachable!()
2451 };
2452 {
2453 let outer = *outer as usize;
2454 let axis_dim = *axis_dim as usize;
2455 let num_idx = *num_idx as usize;
2456 let trailing = *trailing as usize;
2457 unsafe {
2458 if *table_bytes == 8 {
2459 let tab = sl_i64(*table, base, outer * axis_dim * trailing);
2460 let out = sl_mut_i64(*dst, base, outer * num_idx * trailing);
2461 for o in 0..outer {
2462 let tab_outer = o * axis_dim * trailing;
2463 let out_outer = o * num_idx * trailing;
2464 if *idx_i64 != 0 {
2465 let ids = sl_i64(*idx, base, num_idx);
2466 for k in 0..num_idx {
2467 let row = ids[k].max(0) as usize;
2468 if row < axis_dim {
2469 let tab_row = tab_outer + row * trailing;
2470 let out_row = out_outer + k * trailing;
2471 out[out_row..out_row + trailing]
2472 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2473 }
2474 }
2475 } else {
2476 let ids = sl(*idx, base, num_idx);
2477 for k in 0..num_idx {
2478 let row = ids[k] as usize;
2479 if row < axis_dim {
2480 let tab_row = tab_outer + row * trailing;
2481 let out_row = out_outer + k * trailing;
2482 out[out_row..out_row + trailing]
2483 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2484 }
2485 }
2486 }
2487 }
2488 } else {
2489 let tab = sl(*table, base, outer * axis_dim * trailing);
2490 let out = sl_mut(*dst, base, outer * num_idx * trailing);
2491 for o in 0..outer {
2492 let tab_outer = o * axis_dim * trailing;
2493 let out_outer = o * num_idx * trailing;
2494 if *idx_i64 != 0 {
2495 let ids = sl_i64(*idx, base, num_idx);
2496 for k in 0..num_idx {
2497 let row = ids[k].max(0) as usize;
2498 if row < axis_dim {
2499 let tab_row = tab_outer + row * trailing;
2500 let out_row = out_outer + k * trailing;
2501 out[out_row..out_row + trailing]
2502 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2503 }
2504 }
2505 } else {
2506 let ids = sl(*idx, base, num_idx);
2507 for k in 0..num_idx {
2508 let row = ids[k] as usize;
2509 if row < axis_dim {
2510 let tab_row = tab_outer + row * trailing;
2511 let out_row = out_outer + k * trailing;
2512 out[out_row..out_row + trailing]
2513 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
2514 }
2515 }
2516 }
2517 }
2518 }
2519 }
2520 }
2521}
2522
2523#[inline(always)]
2524pub(crate) fn exec_reverse(t: &Thunk, base: *mut u8) {
2525 let Thunk::Reverse {
2526 src,
2527 dst,
2528 dims,
2529 rev_mask,
2530 elem_bytes,
2531 } = t
2532 else {
2533 unreachable!()
2534 };
2535 {
2536 let eb = *elem_bytes as usize;
2537 let rank = dims.len();
2538 let total: usize = dims.iter().map(|&d| d as usize).product::<usize>().max(1);
2539 let mut strides = vec![1usize; rank];
2540 for i in (0..rank.saturating_sub(1)).rev() {
2541 strides[i] = strides[i + 1] * dims[i + 1] as usize;
2542 }
2543 unsafe {
2544 let src_base = base.add(*src);
2545 let dst_base = base.add(*dst);
2546 for o in 0..total {
2547 let mut rem = o;
2548 let mut in_flat = 0usize;
2549 for ax in 0..rank {
2550 let idx = rem / strides[ax];
2551 rem %= strides[ax];
2552 let in_idx = if rev_mask[ax] {
2553 dims[ax] as usize - 1 - idx
2554 } else {
2555 idx
2556 };
2557 in_flat += in_idx * strides[ax];
2558 }
2559 std::ptr::copy_nonoverlapping(src_base.add(in_flat * eb), dst_base.add(o * eb), eb);
2560 }
2561 }
2562 }
2563}
2564
2565pub unsafe fn execute_reverse(
2571 src: usize,
2572 dst: usize,
2573 dims: &[u32],
2574 rev_mask: &[bool],
2575 elem_bytes: usize,
2576 base: *mut u8,
2577) {
2578 let rank = dims.len();
2579 let total: usize = dims.iter().map(|&d| d as usize).product::<usize>().max(1);
2580 let mut strides = vec![1usize; rank];
2581 for i in (0..rank.saturating_sub(1)).rev() {
2582 strides[i] = strides[i + 1] * dims[i + 1] as usize;
2583 }
2584 unsafe {
2585 let src_base = base.add(src);
2586 let dst_base = base.add(dst);
2587 for o in 0..total {
2588 let mut rem = o;
2589 let mut in_flat = 0usize;
2590 for ax in 0..rank {
2591 let idx = rem / strides[ax];
2592 rem %= strides[ax];
2593 let in_idx = if rev_mask[ax] {
2594 dims[ax] as usize - 1 - idx
2595 } else {
2596 idx
2597 };
2598 in_flat += in_idx * strides[ax];
2599 }
2600 std::ptr::copy_nonoverlapping(
2601 src_base.add(in_flat * elem_bytes),
2602 dst_base.add(o * elem_bytes),
2603 elem_bytes,
2604 );
2605 }
2606 }
2607}
2608
2609#[allow(clippy::too_many_arguments)]
2617pub unsafe fn execute_pad(
2618 src: usize,
2619 dst: usize,
2620 in_dims: &[u32],
2621 before: &[u32],
2622 after: &[u32],
2623 mode: rlx_ir::PadMode,
2624 fill: &[u8],
2625 elem_bytes: usize,
2626 base: *mut u8,
2627) {
2628 use rlx_ir::PadMode;
2629 let rank = in_dims.len();
2630 let in_dims: Vec<usize> = in_dims.iter().map(|&d| d as usize).collect();
2631 let out_dims: Vec<usize> = (0..rank)
2632 .map(|i| in_dims[i] + before[i] as usize + after[i] as usize)
2633 .collect();
2634 let total: usize = out_dims.iter().product::<usize>().max(1);
2635 let mut in_strides = vec![1usize; rank];
2636 let mut out_strides = vec![1usize; rank];
2637 for i in (0..rank.saturating_sub(1)).rev() {
2638 in_strides[i] = in_strides[i + 1] * in_dims[i + 1];
2639 out_strides[i] = out_strides[i + 1] * out_dims[i + 1];
2640 }
2641 unsafe {
2642 let src_base = base.add(src);
2643 let dst_base = base.add(dst);
2644 for o in 0..total {
2645 let mut rem = o;
2646 let mut in_flat = 0usize;
2647 let mut is_fill = false;
2648 for ax in 0..rank {
2649 let oc = (rem / out_strides[ax]) as isize;
2650 rem %= out_strides[ax];
2651 if before[ax] == 0 && after[ax] == 0 {
2653 in_flat += oc as usize * in_strides[ax];
2654 continue;
2655 }
2656 let n = in_dims[ax] as isize;
2657 let p = oc - before[ax] as isize;
2658 let ic: isize = match mode {
2659 PadMode::Constant(_) => {
2660 if p < 0 || p >= n {
2661 is_fill = true;
2662 break;
2663 }
2664 p
2665 }
2666 PadMode::Replicate => p.clamp(0, n - 1),
2667 PadMode::Circular => ((p % n) + n) % n,
2668 PadMode::Reflect => {
2669 let period = 2 * (n - 1);
2670 let mut i = ((p % period) + period) % period;
2671 if i >= n {
2672 i = period - i;
2673 }
2674 i
2675 }
2676 };
2677 in_flat += ic as usize * in_strides[ax];
2678 }
2679 let dptr = dst_base.add(o * elem_bytes);
2680 if is_fill {
2681 std::ptr::copy_nonoverlapping(fill.as_ptr(), dptr, elem_bytes);
2682 } else {
2683 std::ptr::copy_nonoverlapping(src_base.add(in_flat * elem_bytes), dptr, elem_bytes);
2684 }
2685 }
2686 }
2687}
2688
2689#[allow(clippy::too_many_arguments)]
2693pub unsafe fn execute_slice(
2694 src: usize,
2695 dst: usize,
2696 in_dims: &[u32],
2697 axis: usize,
2698 start: usize,
2699 len: usize,
2700 step: i64,
2701 elem_bytes: usize,
2702 base: *mut u8,
2703) {
2704 let rank = in_dims.len();
2705 let in_dims: Vec<usize> = in_dims.iter().map(|&d| d as usize).collect();
2706 let mut out_dims = in_dims.clone();
2707 out_dims[axis] = len;
2708 let total: usize = out_dims.iter().product::<usize>().max(1);
2709 let mut in_strides = vec![1usize; rank];
2710 let mut out_strides = vec![1usize; rank];
2711 for i in (0..rank.saturating_sub(1)).rev() {
2712 in_strides[i] = in_strides[i + 1] * in_dims[i + 1];
2713 out_strides[i] = out_strides[i + 1] * out_dims[i + 1];
2714 }
2715 unsafe {
2716 let src_base = base.add(src);
2717 let dst_base = base.add(dst);
2718 for o in 0..total {
2719 let mut rem = o;
2720 let mut in_flat = 0usize;
2721 for ax in 0..rank {
2722 let oc = rem / out_strides[ax];
2723 rem %= out_strides[ax];
2724 let ic = if ax == axis {
2725 (start as i64 + oc as i64 * step) as usize
2726 } else {
2727 oc
2728 };
2729 in_flat += ic * in_strides[ax];
2730 }
2731 std::ptr::copy_nonoverlapping(
2732 src_base.add(in_flat * elem_bytes),
2733 dst_base.add(o * elem_bytes),
2734 elem_bytes,
2735 );
2736 }
2737 }
2738}
2739
2740pub unsafe fn execute_gather_backward_f32(
2741 dy: usize,
2742 indices: usize,
2743 dst: usize,
2744 outer: u32,
2745 axis_dim: u32,
2746 num_idx: u32,
2747 trailing: u32,
2748 base: *mut u8,
2749) {
2750 let (outer, axis_dim, num_idx, trailing) = (
2751 outer as usize,
2752 axis_dim as usize,
2753 num_idx as usize,
2754 trailing as usize,
2755 );
2756 let out = sl_mut(dst, base, outer * axis_dim * trailing);
2757 out.fill(0.0);
2758 crate::training_bwd::gather_axis_backward(
2759 sl(dy, base, outer * num_idx * trailing),
2760 sl(indices, base, num_idx),
2761 out,
2762 outer,
2763 axis_dim,
2764 num_idx,
2765 trailing,
2766 );
2767}
2768
2769#[inline]
2772pub(crate) fn region_activation_scalar(act: rlx_ir::op::Activation, x: f32) -> f32 {
2773 use rlx_ir::op::Activation as A;
2774 const GC: f32 = 0.797_884_6; match act {
2776 A::Relu => x.max(0.0),
2777 A::Gelu | A::GeluApprox => 0.5 * x * (1.0 + (GC * (x + 0.044715 * x * x * x)).tanh()),
2778 A::Silu => x / (1.0 + (-x).exp()),
2779 A::Sigmoid => 1.0 / (1.0 + (-x).exp()),
2780 A::Tanh => x.tanh(),
2781 A::Exp => x.exp(),
2782 A::Log => x.ln(),
2783 A::Sqrt => x.sqrt(),
2784 A::Rsqrt => 1.0 / x.sqrt(),
2785 A::Neg => -x,
2786 A::Abs => x.abs(),
2787 A::Sin => x.sin(),
2788 A::Cos => x.cos(),
2789 A::Tan => x.tan(),
2790 A::Atan => x.atan(),
2791 A::Recip => 1.0 / x,
2792 A::Round => x.round(),
2793 A::Floor => x.floor(),
2794 A::Ceil => x.ceil(),
2795 A::Sign => {
2796 if x > 0.0 {
2797 1.0
2798 } else if x < 0.0 {
2799 -1.0
2800 } else {
2801 0.0
2802 }
2803 }
2804 A::Softplus => x.max(0.0) + (-(x.abs())).exp().ln_1p(),
2805 A::Elu => {
2806 if x > 0.0 {
2807 x
2808 } else {
2809 x.exp() - 1.0
2810 }
2811 }
2812 A::Erf => erf_f32(x),
2813 A::HardSwish => x * (x + 3.0).clamp(0.0, 6.0) / 6.0,
2814 A::HardSigmoid => (x / 6.0 + 0.5).clamp(0.0, 1.0),
2815 A::Mish => x * (x.max(0.0) + (-(x.abs())).exp().ln_1p()).tanh(),
2816 A::Softsign => x / (1.0 + x.abs()),
2817 A::LogSigmoid => x.min(0.0) - (-(x.abs())).exp().ln_1p(),
2818 }
2819}
2820
2821#[inline]
2822pub(crate) fn region_binary_scalar(op: rlx_ir::op::BinaryOp, l: f32, r: f32) -> f32 {
2823 use rlx_ir::op::BinaryOp as B;
2824 match op {
2825 B::Add => l + r,
2826 B::Sub => l - r,
2827 B::Mul => l * r,
2828 B::Div => l / r,
2829 B::Max => l.max(r),
2830 B::Min => l.min(r),
2831 B::Pow => l.powf(r),
2832 B::Mod => l % r,
2833 B::Atan2 => l.atan2(r),
2834 B::BitAnd => ((l as i64) & (r as i64)) as f32,
2835 B::BitOr => ((l as i64) | (r as i64)) as f32,
2836 B::BitXor => ((l as i64) ^ (r as i64)) as f32,
2837 B::Shl => (l as i64).wrapping_shl(r as u32) as f32,
2838 B::Shr => (l as i64).wrapping_shr(r as u32) as f32,
2839 }
2840}
2841
2842pub(crate) fn region_cast_scalar(to: rlx_ir::DType, v: f32) -> f32 {
2854 use rlx_ir::DType as DT;
2855 match to {
2856 DT::F32 | DT::F64 | DT::C64 | DT::C128 => v,
2857 DT::F16 => half::f16::from_f32(v).to_f32(),
2858 DT::BF16 => half::bf16::from_f32(v).to_f32(),
2859 DT::I8 => v as i8 as f32,
2860 DT::I16 => v as i16 as f32,
2861 DT::I32 => v as i32 as f32,
2862 DT::I64 => v as i64 as f32,
2863 DT::U8 => v as u8 as f32,
2864 DT::U32 => v as u32 as f32,
2865 DT::Bool => f32::from(u8::from(v != 0.0)),
2866 }
2867}
2868
2869#[inline]
2870pub(crate) fn region_compare_scalar(op: rlx_ir::op::CmpOp, l: f32, r: f32) -> bool {
2871 use rlx_ir::op::CmpOp as C;
2872 match op {
2873 C::Eq => l == r,
2874 C::Ne => l != r,
2875 C::Lt => l < r,
2876 C::Le => l <= r,
2877 C::Gt => l > r,
2878 C::Ge => l >= r,
2879 }
2880}
2881
2882#[inline]
2886pub(crate) fn region_resolve_operand(
2887 op: &rlx_ir::op::ChainOperand,
2888 gid: usize,
2889 base: *const u8,
2890 input_offs: &[usize],
2891 in_lanes: &[crate::thunk::types::LaneKind],
2892 scalar_mask: u32,
2893 modulus: &[u32; 16],
2894 scratch: &[f32; 32],
2895) -> f32 {
2896 use rlx_ir::op::ChainOperand as O;
2897 match op {
2898 O::Step(s) => scratch[*s as usize],
2899 O::Input(i) => {
2900 let i = *i as usize;
2901 let row = if (scalar_mask >> i) & 1 == 1 {
2902 0
2903 } else if modulus[i] != 0 {
2904 gid % modulus[i] as usize
2905 } else {
2906 gid
2907 };
2908 let lane = in_lanes
2911 .get(i)
2912 .copied()
2913 .unwrap_or(crate::thunk::types::LaneKind::F32);
2914 unsafe { lane.load(base, input_offs[i], row) }
2915 }
2916 }
2917}
2918
2919#[inline]
2922pub(crate) fn region_eval_elem(
2923 gid: usize,
2924 base: *const u8,
2925 input_offs: &[usize],
2926 in_lanes: &[crate::thunk::types::LaneKind],
2927 chain: &[rlx_ir::op::ChainStep],
2928 scalar_mask: u32,
2929 modulus: &[u32; 16],
2930) -> f32 {
2931 use rlx_ir::op::ChainStep as S;
2932 let mut scratch = [0f32; 32];
2933 let r = |o: &rlx_ir::op::ChainOperand, sc: &[f32; 32]| {
2934 region_resolve_operand(o, gid, base, input_offs, in_lanes, scalar_mask, modulus, sc)
2935 };
2936 for (k, step) in chain.iter().enumerate() {
2937 scratch[k] = match step {
2938 S::Activation(a, x) => region_activation_scalar(*a, r(x, &scratch)),
2939 S::Cast(to, x) => region_cast_scalar(*to, r(x, &scratch)),
2940 S::Binary(op, l, rr) => region_binary_scalar(*op, r(l, &scratch), r(rr, &scratch)),
2941 S::Compare(op, l, rr) => {
2942 if region_compare_scalar(*op, r(l, &scratch), r(rr, &scratch)) {
2943 1.0
2944 } else {
2945 0.0
2946 }
2947 }
2948 S::Where(c, t, f) => {
2949 if r(c, &scratch) != 0.0 {
2950 r(t, &scratch)
2951 } else {
2952 r(f, &scratch)
2953 }
2954 }
2955 };
2956 }
2957 scratch[chain.len() - 1]
2958}
2959
2960#[inline(always)]
2965pub(crate) fn apply_activation_inplace(d: &mut [f32], act: rlx_ir::op::Activation) {
2966 use rlx_ir::op::Activation;
2967 match act {
2968 Activation::Gelu => crate::kernels::par_gelu_inplace(d),
2969 Activation::GeluApprox => crate::kernels::par_gelu_approx_inplace(d),
2970 Activation::Silu => crate::kernels::par_silu_inplace(d),
2971 Activation::Relu => {
2972 for v in d.iter_mut() {
2973 *v = v.max(0.0);
2974 }
2975 }
2976 Activation::Sigmoid => {
2977 let x = d.to_vec();
2978 crate::vmath::vvsigmoidf(d, &x);
2979 }
2980 Activation::Tanh => crate::vmath::vvtanhf_hot_inplace(d),
2981 Activation::Exp => crate::vmath::vvexpf_hot_inplace(d),
2982 Activation::Log => crate::vmath::vvlogf_inplace(d),
2983 Activation::Sqrt => crate::vmath::vvsqrtf_inplace(d),
2984 Activation::Rsqrt => crate::vmath::vvrsqrtf_inplace(d),
2985 Activation::Neg => {
2986 for v in d.iter_mut() {
2987 *v = -*v;
2988 }
2989 }
2990 Activation::Abs => {
2991 for v in d.iter_mut() {
2992 *v = v.abs();
2993 }
2994 }
2995 Activation::Round => {
2996 for v in d.iter_mut() {
2997 *v = v.round();
2998 }
2999 }
3000 Activation::Sin => {
3001 for v in d.iter_mut() {
3002 *v = v.sin();
3003 }
3004 }
3005 Activation::Cos => {
3006 for v in d.iter_mut() {
3007 *v = v.cos();
3008 }
3009 }
3010 Activation::Tan => {
3011 for v in d.iter_mut() {
3012 *v = v.tan();
3013 }
3014 }
3015 Activation::Atan => {
3016 for v in d.iter_mut() {
3017 *v = v.atan();
3018 }
3019 }
3020 Activation::Recip => crate::vmath::vvrecf_inplace(d),
3021 Activation::Floor => {
3022 for v in d.iter_mut() {
3023 *v = v.floor();
3024 }
3025 }
3026 Activation::Ceil => {
3027 for v in d.iter_mut() {
3028 *v = v.ceil();
3029 }
3030 }
3031 Activation::Sign => {
3032 for v in d.iter_mut() {
3033 *v = if *v > 0.0 {
3034 1.0
3035 } else if *v < 0.0 {
3036 -1.0
3037 } else {
3038 0.0
3039 };
3040 }
3041 }
3042 Activation::Softplus => {
3043 for v in d.iter_mut() {
3044 *v = v.max(0.0) + (-(v.abs())).exp().ln_1p();
3045 }
3046 }
3047 Activation::Elu => {
3048 for v in d.iter_mut() {
3049 *v = if *v > 0.0 { *v } else { v.exp() - 1.0 };
3050 }
3051 }
3052 Activation::Erf => {
3053 for v in d.iter_mut() {
3054 *v = erf_f32(*v);
3055 }
3056 }
3057 Activation::HardSwish => {
3058 for v in d.iter_mut() {
3059 let x = *v;
3060 *v = x * (x + 3.0).clamp(0.0, 6.0) / 6.0;
3061 }
3062 }
3063 Activation::HardSigmoid => {
3064 for v in d.iter_mut() {
3065 *v = (*v / 6.0 + 0.5).clamp(0.0, 1.0);
3066 }
3067 }
3068 Activation::Mish => {
3069 for v in d.iter_mut() {
3070 let x = *v;
3071 *v = x * (x.max(0.0) + (-(x.abs())).exp().ln_1p()).tanh();
3072 }
3073 }
3074 Activation::Softsign => {
3075 for v in d.iter_mut() {
3076 let x = *v;
3077 *v = x / (1.0 + x.abs());
3078 }
3079 }
3080 Activation::LogSigmoid => {
3081 for v in d.iter_mut() {
3082 let x = *v;
3083 *v = x.min(0.0) - (-(x.abs())).exp().ln_1p();
3084 }
3085 }
3086 }
3087}
3088
3089pub(crate) fn activation_backward_kernel(
3090 act: rlx_ir::op::Activation,
3091 xs: &[f32],
3092 dys: &[f32],
3093 out: &mut [f32],
3094) {
3095 use rlx_ir::op::Activation;
3096 let n = xs.len();
3097 debug_assert_eq!(dys.len(), n);
3098 debug_assert_eq!(out.len(), n);
3099 match act {
3100 Activation::Relu => {
3101 for i in 0..n {
3102 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
3103 }
3104 }
3105 Activation::Sigmoid => {
3106 for i in 0..n {
3107 let s = 1.0 / (1.0 + (-xs[i]).exp());
3108 out[i] = s * (1.0 - s) * dys[i];
3109 }
3110 }
3111 Activation::Tanh => {
3112 for i in 0..n {
3113 let t = xs[i].tanh();
3114 out[i] = (1.0 - t * t) * dys[i];
3115 }
3116 }
3117 Activation::Silu => {
3118 for i in 0..n {
3120 let s = 1.0 / (1.0 + (-xs[i]).exp());
3121 out[i] = s * (1.0 + xs[i] * (1.0 - s)) * dys[i];
3122 }
3123 }
3124 Activation::Gelu => {
3125 const INV_SQRT2: f32 = 0.707_106_77;
3128 const INV_SQRT_2PI: f32 = 0.398_942_3;
3129 for i in 0..n {
3130 let x = xs[i];
3131 let phi = 0.5 * (1.0 + erf_f32(x * INV_SQRT2));
3132 let pdf = INV_SQRT_2PI * (-(x * x) * 0.5).exp();
3133 out[i] = (phi + x * pdf) * dys[i];
3134 }
3135 }
3136 Activation::GeluApprox => {
3137 const C: f32 = 0.797_884_6; const A: f32 = 0.044_715;
3141 for i in 0..n {
3142 let x = xs[i];
3143 let inner = C * (x + A * x * x * x);
3144 let t = inner.tanh();
3145 let dinner = C * (1.0 + 3.0 * A * x * x);
3146 let d = 0.5 * (1.0 + t) + 0.5 * x * (1.0 - t * t) * dinner;
3147 out[i] = d * dys[i];
3148 }
3149 }
3150 Activation::Exp => {
3151 for i in 0..n {
3152 out[i] = xs[i].exp() * dys[i];
3153 }
3154 }
3155 Activation::Log => {
3156 for i in 0..n {
3157 out[i] = dys[i] / xs[i];
3158 }
3159 }
3160 Activation::Sqrt => {
3161 for i in 0..n {
3163 let s = xs[i].sqrt();
3164 out[i] = if s > 0.0 { 0.5 * dys[i] / s } else { 0.0 };
3165 }
3166 }
3167 Activation::Rsqrt => {
3168 for i in 0..n {
3170 let s = xs[i].sqrt();
3171 out[i] = if s > 0.0 {
3172 -0.5 * dys[i] / (xs[i] * s)
3173 } else {
3174 0.0
3175 };
3176 }
3177 }
3178 Activation::Neg => {
3179 for i in 0..n {
3180 out[i] = -dys[i];
3181 }
3182 }
3183 Activation::Abs => {
3184 for i in 0..n {
3186 let x = xs[i];
3187 let s = if x > 0.0 {
3188 1.0
3189 } else if x < 0.0 {
3190 -1.0
3191 } else {
3192 0.0
3193 };
3194 out[i] = s * dys[i];
3195 }
3196 }
3197 Activation::Round => {
3198 out.copy_from_slice(dys);
3203 }
3204 Activation::Sin => {
3205 for i in 0..n {
3207 out[i] = xs[i].cos() * dys[i];
3208 }
3209 }
3210 Activation::Cos => {
3211 for i in 0..n {
3212 out[i] = -xs[i].sin() * dys[i];
3213 }
3214 }
3215 Activation::Tan => {
3216 for i in 0..n {
3218 let t = xs[i].tan();
3219 out[i] = (1.0 + t * t) * dys[i];
3220 }
3221 }
3222 Activation::Atan => {
3223 for i in 0..n {
3225 let x = xs[i];
3226 out[i] = dys[i] / (1.0 + x * x);
3227 }
3228 }
3229 Activation::Recip => {
3230 for i in 0..n {
3231 out[i] = -dys[i] / (xs[i] * xs[i]);
3232 }
3233 }
3234 Activation::Floor | Activation::Ceil | Activation::Sign => {
3236 for o in out.iter_mut() {
3237 *o = 0.0;
3238 }
3239 }
3240 Activation::Softplus => {
3241 for i in 0..n {
3242 out[i] = dys[i] / (1.0 + (-xs[i]).exp());
3243 }
3244 }
3245 Activation::Elu => {
3246 for i in 0..n {
3247 out[i] = if xs[i] > 0.0 {
3248 dys[i]
3249 } else {
3250 dys[i] * xs[i].exp()
3251 };
3252 }
3253 }
3254 Activation::Erf
3256 | Activation::HardSwish
3257 | Activation::HardSigmoid
3258 | Activation::Mish
3259 | Activation::Softsign
3260 | Activation::LogSigmoid => {
3261 panic!("activation_backward_kernel: {act:?} is decomposed at the AD level")
3262 }
3263 }
3264}
3265
3266pub(crate) fn activation_backward_kernel_f64(
3270 act: rlx_ir::op::Activation,
3271 xs: &[f64],
3272 dys: &[f64],
3273 out: &mut [f64],
3274) {
3275 use rlx_ir::op::Activation;
3276 let n = xs.len();
3277 debug_assert_eq!(dys.len(), n);
3278 debug_assert_eq!(out.len(), n);
3279 match act {
3280 Activation::Relu => {
3281 for i in 0..n {
3282 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
3283 }
3284 }
3285 Activation::Sigmoid => {
3286 for i in 0..n {
3287 let s = 1.0 / (1.0 + (-xs[i]).exp());
3288 out[i] = s * (1.0 - s) * dys[i];
3289 }
3290 }
3291 Activation::Tanh => {
3292 for i in 0..n {
3293 let t = xs[i].tanh();
3294 out[i] = (1.0 - t * t) * dys[i];
3295 }
3296 }
3297 Activation::Silu => {
3298 for i in 0..n {
3299 let s = 1.0 / (1.0 + (-xs[i]).exp());
3300 out[i] = s * (1.0 + xs[i] * (1.0 - s)) * dys[i];
3301 }
3302 }
3303 Activation::Gelu | Activation::GeluApprox => {
3304 const INV_SQRT2: f64 = std::f64::consts::FRAC_1_SQRT_2;
3306 const INV_SQRT_2PI: f64 = 0.398_942_280_401_432_7;
3307 for i in 0..n {
3308 let x = xs[i];
3309 let phi = 0.5 * (1.0 + erf_f64(x * INV_SQRT2));
3310 let pdf = INV_SQRT_2PI * (-(x * x) * 0.5).exp();
3311 out[i] = (phi + x * pdf) * dys[i];
3312 }
3313 }
3314 Activation::Exp => {
3315 for i in 0..n {
3316 out[i] = xs[i].exp() * dys[i];
3317 }
3318 }
3319 Activation::Log => {
3320 for i in 0..n {
3321 out[i] = dys[i] / xs[i];
3322 }
3323 }
3324 Activation::Sqrt => {
3325 for i in 0..n {
3326 let s = xs[i].sqrt();
3327 out[i] = if s > 0.0 { 0.5 * dys[i] / s } else { 0.0 };
3328 }
3329 }
3330 Activation::Rsqrt => {
3331 for i in 0..n {
3332 let s = xs[i].sqrt();
3333 out[i] = if s > 0.0 {
3334 -0.5 * dys[i] / (xs[i] * s)
3335 } else {
3336 0.0
3337 };
3338 }
3339 }
3340 Activation::Neg => {
3341 for i in 0..n {
3342 out[i] = -dys[i];
3343 }
3344 }
3345 Activation::Abs => {
3346 for i in 0..n {
3347 let x = xs[i];
3348 let s = if x > 0.0 {
3349 1.0
3350 } else if x < 0.0 {
3351 -1.0
3352 } else {
3353 0.0
3354 };
3355 out[i] = s * dys[i];
3356 }
3357 }
3358 Activation::Round => {
3359 out.copy_from_slice(dys);
3360 }
3361 Activation::Sin => {
3362 for i in 0..n {
3363 out[i] = xs[i].cos() * dys[i];
3364 }
3365 }
3366 Activation::Cos => {
3367 for i in 0..n {
3368 out[i] = -xs[i].sin() * dys[i];
3369 }
3370 }
3371 Activation::Tan => {
3372 for i in 0..n {
3373 let t = xs[i].tan();
3374 out[i] = (1.0 + t * t) * dys[i];
3375 }
3376 }
3377 Activation::Atan => {
3378 for i in 0..n {
3379 let x = xs[i];
3380 out[i] = dys[i] / (1.0 + x * x);
3381 }
3382 }
3383 Activation::Recip => {
3384 for i in 0..n {
3385 out[i] = -dys[i] / (xs[i] * xs[i]);
3386 }
3387 }
3388 Activation::Floor | Activation::Ceil | Activation::Sign => {
3390 for o in out.iter_mut() {
3391 *o = 0.0;
3392 }
3393 }
3394 Activation::Softplus => {
3395 for i in 0..n {
3396 out[i] = dys[i] / (1.0 + (-xs[i]).exp());
3397 }
3398 }
3399 Activation::Elu => {
3400 for i in 0..n {
3401 out[i] = if xs[i] > 0.0 {
3402 dys[i]
3403 } else {
3404 dys[i] * xs[i].exp()
3405 };
3406 }
3407 }
3408 Activation::Erf
3410 | Activation::HardSwish
3411 | Activation::HardSigmoid
3412 | Activation::Mish
3413 | Activation::Softsign
3414 | Activation::LogSigmoid => {
3415 panic!("activation_backward_kernel: {act:?} is decomposed at the AD level")
3416 }
3417 }
3418}
3419
3420#[inline(always)]
3425pub(crate) fn erf_f64(x: f64) -> f64 {
3426 let s = x.signum();
3427 let x = x.abs();
3428 let t = 1.0 / (1.0 + 0.327_591_1 * x);
3429 let y = 1.0
3430 - (((((1.061_405_43 * t - 1.453_152_03) * t) + 1.421_413_75) * t - 0.284_496_74) * t
3431 + 0.254_829_59)
3432 * t
3433 * (-x * x).exp();
3437 s * y
3438}
3439
3440#[inline(always)]
3443pub(crate) fn erf_f32(x: f32) -> f32 {
3444 let s = x.signum();
3445 let x = x.abs();
3446 let t = 1.0 / (1.0 + 0.327_591_1 * x);
3447 let y = 1.0
3448 - (((((1.061_405_4 * t - 1.453_152_1) * t) + 1.421_413_8) * t - 0.284_496_74) * t
3449 + 0.254_829_6)
3450 * t
3451 * crate::vmath::exp_poly(-x * x);
3452 s * y
3453}
3454
3455pub(crate) fn narrow_thunk_closure(
3456 src: usize,
3457 dst: usize,
3458 outer: u32,
3459 src_stride: u32,
3460 dst_stride: u32,
3461 inner: u32,
3462 elem_bytes: u8,
3463) -> Arc<dyn Fn(*mut u8) + Send + Sync> {
3464 let (outer, ss, ds, inner, eb) = (
3465 outer as usize,
3466 src_stride as usize,
3467 dst_stride as usize,
3468 inner as usize,
3469 elem_bytes as usize,
3470 );
3471 let row_bytes = inner.saturating_mul(eb);
3472 let src_row_stride = ss.saturating_mul(eb);
3473 let dst_row_stride = ds.saturating_mul(eb);
3474 Arc::new(move |base: *mut u8| unsafe {
3475 if row_bytes == 0 || src == dst {
3476 return;
3477 }
3478 let arena_len = usize::MAX;
3480 for o in 0..outer {
3481 let s_off = src + o * src_row_stride;
3482 let d_off = dst + o * dst_row_stride;
3483 if s_off == d_off {
3484 continue;
3485 }
3486 if s_off.saturating_add(row_bytes) > arena_len
3487 || d_off.saturating_add(row_bytes) > arena_len
3488 {
3489 break;
3490 }
3491 std::ptr::copy_nonoverlapping(base.add(s_off), base.add(d_off), row_bytes);
3492 }
3493 })
3494}
3495
3496pub(crate) fn transpose_walk_f64(
3500 inp: &[f64],
3501 out: &mut [f64],
3502 out_dims: &[u32],
3503 in_strides: &[u32],
3504) {
3505 let rank = out_dims.len();
3506 let mut idx = vec![0u32; rank];
3507 for o in 0..out.len() {
3508 let mut src_off = 0usize;
3509 for d in 0..rank {
3510 src_off += idx[d] as usize * in_strides[d] as usize;
3511 }
3512 out[o] = inp[broadcast_src_index(src_off, inp.len())];
3513 for d in (0..rank).rev() {
3515 idx[d] += 1;
3516 if idx[d] < out_dims[d] {
3517 break;
3518 }
3519 idx[d] = 0;
3520 }
3521 }
3522}
3523
3524pub(crate) fn apply_activation_f64(inp: &[f64], out: &mut [f64], kind: Activation) {
3530 match kind {
3531 Activation::Neg => {
3532 for (o, &v) in out.iter_mut().zip(inp) {
3533 *o = -v;
3534 }
3535 }
3536 Activation::Exp => {
3537 for (o, &v) in out.iter_mut().zip(inp) {
3538 *o = v.exp();
3539 }
3540 }
3541 Activation::Log => {
3542 for (o, &v) in out.iter_mut().zip(inp) {
3543 *o = v.ln();
3544 }
3545 }
3546 Activation::Sqrt => {
3547 for (o, &v) in out.iter_mut().zip(inp) {
3548 *o = v.sqrt();
3549 }
3550 }
3551 Activation::Rsqrt => {
3552 for (o, &v) in out.iter_mut().zip(inp) {
3553 *o = 1.0 / v.sqrt();
3554 }
3555 }
3556 Activation::Abs => {
3557 for (o, &v) in out.iter_mut().zip(inp) {
3558 *o = v.abs();
3559 }
3560 }
3561 Activation::Tanh => {
3562 for (o, &v) in out.iter_mut().zip(inp) {
3563 *o = v.tanh();
3564 }
3565 }
3566 Activation::Sigmoid => {
3567 for (o, &v) in out.iter_mut().zip(inp) {
3568 *o = 1.0 / (1.0 + (-v).exp());
3569 }
3570 }
3571 Activation::Relu => {
3572 for (o, &v) in out.iter_mut().zip(inp) {
3573 *o = v.max(0.0);
3574 }
3575 }
3576 Activation::Round => {
3577 for (o, &v) in out.iter_mut().zip(inp) {
3578 *o = v.round_ties_even();
3579 }
3580 }
3581 Activation::Sin => {
3582 for (o, &v) in out.iter_mut().zip(inp) {
3583 *o = v.sin();
3584 }
3585 }
3586 Activation::Cos => {
3587 for (o, &v) in out.iter_mut().zip(inp) {
3588 *o = v.cos();
3589 }
3590 }
3591 Activation::Tan => {
3592 for (o, &v) in out.iter_mut().zip(inp) {
3593 *o = v.tan();
3594 }
3595 }
3596 Activation::Atan => {
3597 for (o, &v) in out.iter_mut().zip(inp) {
3598 *o = v.atan();
3599 }
3600 }
3601 Activation::Recip => {
3602 for (o, &v) in out.iter_mut().zip(inp) {
3603 *o = 1.0 / v;
3604 }
3605 }
3606 Activation::Floor => {
3607 for (o, &v) in out.iter_mut().zip(inp) {
3608 *o = v.floor();
3609 }
3610 }
3611 Activation::Ceil => {
3612 for (o, &v) in out.iter_mut().zip(inp) {
3613 *o = v.ceil();
3614 }
3615 }
3616 Activation::Sign => {
3617 for (o, &v) in out.iter_mut().zip(inp) {
3618 *o = if v > 0.0 {
3619 1.0
3620 } else if v < 0.0 {
3621 -1.0
3622 } else {
3623 0.0
3624 };
3625 }
3626 }
3627 Activation::Softplus => {
3628 for (o, &v) in out.iter_mut().zip(inp) {
3629 *o = v.max(0.0) + (-(v.abs())).exp().ln_1p();
3630 }
3631 }
3632 Activation::Elu => {
3633 for (o, &v) in out.iter_mut().zip(inp) {
3634 *o = if v > 0.0 { v } else { v.exp() - 1.0 };
3635 }
3636 }
3637 Activation::Erf => {
3638 for (o, &v) in out.iter_mut().zip(inp) {
3639 *o = erf_f64(v);
3640 }
3641 }
3642 Activation::HardSwish => {
3643 for (o, &v) in out.iter_mut().zip(inp) {
3644 *o = v * (v + 3.0).clamp(0.0, 6.0) / 6.0;
3645 }
3646 }
3647 Activation::HardSigmoid => {
3648 for (o, &v) in out.iter_mut().zip(inp) {
3649 *o = (v / 6.0 + 0.5).clamp(0.0, 1.0);
3650 }
3651 }
3652 Activation::Mish => {
3653 for (o, &v) in out.iter_mut().zip(inp) {
3654 *o = v * (v.max(0.0) + (-(v.abs())).exp().ln_1p()).tanh();
3655 }
3656 }
3657 Activation::Softsign => {
3658 for (o, &v) in out.iter_mut().zip(inp) {
3659 *o = v / (1.0 + v.abs());
3660 }
3661 }
3662 Activation::LogSigmoid => {
3663 for (o, &v) in out.iter_mut().zip(inp) {
3664 *o = v.min(0.0) - (-(v.abs())).exp().ln_1p();
3665 }
3666 }
3667 Activation::Gelu | Activation::GeluApprox | Activation::Silu => {
3668 panic!(
3669 "apply_activation_f64: {kind:?} not yet implemented at f64. \
3670 Add when a workload needs it."
3671 );
3672 }
3673 }
3674}
3675
3676#[inline]
3677pub(crate) fn binary_op_f64(op: BinaryOp, a: f64, b: f64) -> f64 {
3678 match op {
3679 BinaryOp::Add => a + b,
3680 BinaryOp::Sub => a - b,
3681 BinaryOp::Mul => a * b,
3682 BinaryOp::Div => a / b,
3683 BinaryOp::Max => a.max(b),
3684 BinaryOp::Min => a.min(b),
3685 BinaryOp::Pow => a.powf(b),
3686 BinaryOp::Mod => a % b,
3687 BinaryOp::Atan2 => a.atan2(b),
3688 BinaryOp::BitAnd => ((a as i64) & (b as i64)) as f64,
3689 BinaryOp::BitOr => ((a as i64) | (b as i64)) as f64,
3690 BinaryOp::BitXor => ((a as i64) ^ (b as i64)) as f64,
3691 BinaryOp::Shl => ((a as i64) << (b as i64)) as f64,
3692 BinaryOp::Shr => ((a as i64) >> (b as i64)) as f64,
3693 }
3694}
3695
3696pub(crate) fn half_slot_is_widened(
3703 graph: &Graph,
3704 arena: &crate::arena::Arena,
3705 id: rlx_ir::NodeId,
3706) -> bool {
3707 let node = graph.node(id);
3708 if !matches!(node.shape.dtype(), rlx_ir::DType::BF16 | rlx_ir::DType::F16) {
3709 return false;
3710 }
3711 if matches!(node.op, rlx_ir::Op::Param { .. }) {
3712 return false;
3713 }
3714 let n = node.shape.num_elements().unwrap_or(0);
3715 if n == 0 || !arena.has_buffer(id) {
3716 return false;
3717 }
3718 arena.byte_size(id) / n >= 4
3719}