1#![allow(unsafe_op_in_unsafe_fn)]
2use crate::thunk::*;
3
4#[allow(unused_variables)]
5pub(crate) fn compile_gather(
6 node: &rlx_ir::Node,
7 graph: &Graph,
8 arena: &crate::arena::Arena,
9 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
10 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
11 rng: rlx_ir::RngOptions,
12) -> Thunk {
13 let Op::Gather { axis } = &node.op else {
14 unreachable!()
15 };
16 {
17 let table_shape = &graph.node(node.inputs[0]).shape;
19 let rank = table_shape.rank();
20 let outer: usize = (0..*axis)
21 .map(|i| table_shape.dim(i).unwrap_static())
22 .product::<usize>()
23 .max(1);
24 let trailing: usize = (*axis + 1..rank)
25 .map(|i| table_shape.dim(i).unwrap_static())
26 .product::<usize>()
27 .max(1);
28 let axis_dim = table_shape.dim(*axis).unwrap_static();
29 let idx_len = get_len(graph, node.inputs[1]);
30 let idx_i64 = u8::from(graph.node(node.inputs[1]).shape.dtype() == rlx_ir::DType::I64);
31 let table_bytes = graph.node(node.inputs[0]).shape.dtype().size_bytes() as u8;
32 Thunk::GatherAxis {
33 table: node_offset(arena, node.inputs[0]),
34 idx: node_offset(arena, node.inputs[1]),
35 dst: node_offset(arena, node.id),
36 outer: outer as u32,
37 axis_dim: axis_dim as u32,
38 num_idx: idx_len as u32,
39 trailing: trailing as u32,
40 idx_i64,
41 table_bytes,
42 }
43 }
44}
45
46#[allow(unused_variables)]
47pub(crate) fn compile_narrow(
48 node: &rlx_ir::Node,
49 graph: &Graph,
50 arena: &crate::arena::Arena,
51 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
52 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
53 rng: rlx_ir::RngOptions,
54) -> Thunk {
55 let Op::Narrow { axis, start, len } = &node.op else {
56 unreachable!()
57 };
58 {
59 let in_shape = &graph.node(node.inputs[0]).shape;
60 let elem_bytes = in_shape.dtype().size_bytes() as u8;
61 let rank = in_shape.rank();
62 let outer: usize = (0..*axis)
63 .map(|i| in_shape.dim(i).unwrap_static())
64 .product::<usize>()
65 .max(1);
66 let inner: usize = (*axis + 1..rank)
67 .map(|i| in_shape.dim(i).unwrap_static())
68 .product::<usize>()
69 .max(1);
70 let in_axis = in_shape.dim(*axis).unwrap_static();
71 let src_byte_offset =
72 node_offset(arena, node.inputs[0]) + start * inner * elem_bytes as usize;
73 Thunk::Narrow {
74 src: src_byte_offset,
75 dst: node_offset(arena, node.id),
76 outer: outer as u32,
77 src_stride: (in_axis * inner) as u32, dst_stride: (*len * inner) as u32, inner: (*len * inner) as u32, elem_bytes,
81 }
82 }
83}
84
85#[allow(unused_variables)]
86pub(crate) fn compile_reverse(
87 node: &rlx_ir::Node,
88 graph: &Graph,
89 arena: &crate::arena::Arena,
90 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
91 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
92 rng: rlx_ir::RngOptions,
93) -> Thunk {
94 let Op::Reverse { axes } = &node.op else {
95 unreachable!()
96 };
97 {
98 let in_shape = &graph.node(node.inputs[0]).shape;
99 let rank = in_shape.rank();
100 let dims: Vec<u32> = (0..rank)
101 .map(|i| in_shape.dim(i).unwrap_static() as u32)
102 .collect();
103 let mut rev_mask = vec![false; rank];
104 for &a in axes {
105 if a < rank {
106 rev_mask[a] = true;
107 }
108 }
109 Thunk::Reverse {
110 src: node_offset(arena, node.inputs[0]),
111 dst: node_offset(arena, node.id),
112 dims,
113 rev_mask,
114 elem_bytes: in_shape.dtype().size_bytes() as u8,
115 }
116 }
117}
118
119#[allow(unused_variables)]
120pub(crate) fn compile_cast(
121 node: &rlx_ir::Node,
122 graph: &Graph,
123 arena: &crate::arena::Arena,
124 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
125 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
126 rng: rlx_ir::RngOptions,
127) -> Thunk {
128 let Op::Cast { to } = &node.op else {
129 unreachable!()
130 };
131 {
132 let in_node = graph.node(node.inputs[0]);
133 let in_dtype = in_node.shape.dtype();
134 let out_dtype = *to;
135 let len = node.shape.num_elements().unwrap();
136 let src = node_offset(arena, node.inputs[0]);
137 let dst = node_offset(arena, node.id);
138 if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::I64 {
139 Thunk::CastF32ToI64 {
140 src,
141 dst,
142 len: len as u32,
143 }
144 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::F64 {
145 Thunk::CastF32ToF64 {
146 src,
147 dst,
148 len: len as u32,
149 }
150 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::I32 {
151 Thunk::CastF32ToI32 {
152 src,
153 dst,
154 len: len as u32,
155 }
156 } else if in_dtype == rlx_ir::DType::I64 && out_dtype == rlx_ir::DType::F32 {
157 Thunk::CastI64ToF32 {
158 src,
159 dst,
160 len: len as u32,
161 }
162 } else if in_dtype == rlx_ir::DType::Bool && out_dtype == rlx_ir::DType::I32 {
163 Thunk::CastBoolToI32 {
164 src,
165 dst,
166 len: len as u32,
167 }
168 } else if in_dtype == rlx_ir::DType::Bool && out_dtype == rlx_ir::DType::F32 {
169 Thunk::CastBoolToF32 {
172 src,
173 dst,
174 len: len as u32,
175 }
176 } else if in_dtype == rlx_ir::DType::F32 && out_dtype == rlx_ir::DType::Bool {
177 Thunk::CastF32ToBool {
180 src,
181 dst,
182 len: len as u32,
183 }
184 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::F32 {
185 Thunk::CastI32ToF32 {
186 src,
187 dst,
188 len: len as u32,
189 }
190 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::I64 {
191 Thunk::CastI32ToI64 {
192 src,
193 dst,
194 len: len as u32,
195 }
196 } else if in_dtype == rlx_ir::DType::I32 && out_dtype == rlx_ir::DType::Bool {
197 Thunk::CastI32ToBool {
198 src,
199 dst,
200 len: len as u32,
201 }
202 } else if in_dtype == rlx_ir::DType::I64 && out_dtype == rlx_ir::DType::Bool {
203 Thunk::CastI64ToBool {
204 src,
205 dst,
206 len: len as u32,
207 }
208 } else if in_dtype == rlx_ir::DType::Bool && out_dtype == rlx_ir::DType::I64 {
209 Thunk::CastBoolToI64 {
210 src,
211 dst,
212 len: len as u32,
213 }
214 } else if in_dtype == out_dtype {
215 match out_dtype {
216 rlx_ir::DType::F64 => Thunk::CopyF64 {
217 src,
218 dst,
219 len: len as u32,
220 },
221 rlx_ir::DType::I64 => Thunk::CopyI64 {
222 src,
223 dst,
224 len: len as u32,
225 },
226 _ => Thunk::Copy {
227 src,
228 dst,
229 len: len as u32,
230 },
231 }
232 } else {
233 Thunk::Copy {
234 src,
235 dst,
236 len: len as u32,
237 }
238 }
239 }
240}
241
242#[allow(unused_variables)]
243pub(crate) fn compile_expand(
244 node: &rlx_ir::Node,
245 graph: &Graph,
246 arena: &crate::arena::Arena,
247 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
248 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
249 rng: rlx_ir::RngOptions,
250) -> Thunk {
251 let Op::Expand { .. } = &node.op else {
252 unreachable!()
253 };
254 {
255 let in_shape = &graph.node(node.inputs[0]).shape;
260 let out_shape = &node.shape;
261 let in_rank = in_shape.rank();
262 let out_rank = out_shape.rank();
263 let pad = out_rank.saturating_sub(in_rank);
265 let in_dims: Vec<usize> = (0..out_rank)
266 .map(|i| {
267 if i < pad {
268 1
269 } else {
270 in_shape.dim(i - pad).unwrap_static()
271 }
272 })
273 .collect();
274 let mut in_strides_full = vec![1usize; out_rank];
276 for d in (0..out_rank.saturating_sub(1)).rev() {
277 in_strides_full[d] = in_strides_full[d + 1] * in_dims[d + 1];
278 }
279 let out_dims: Vec<u32> = (0..out_rank)
280 .map(|i| out_shape.dim(i).unwrap_static() as u32)
281 .collect();
282 let in_strides: Vec<u32> = (0..out_rank)
284 .map(|i| {
285 if in_dims[i] == 1 && (out_dims[i] as usize) > 1 {
286 0
287 } else {
288 in_strides_full[i] as u32
289 }
290 })
291 .collect();
292 let in_total = in_dims.iter().product::<usize>() as u32;
293 let src = node_offset(arena, node.inputs[0]);
294 let dst = node_offset(arena, node.id);
295 let elem_bytes = node.shape.dtype().size_bytes() as u8;
296 match node.shape.dtype() {
297 rlx_ir::DType::F64 => Thunk::TransposeF64 {
298 src,
299 dst,
300 in_total,
301 out_dims,
302 in_strides,
303 },
304 _ => Thunk::Transpose {
305 src,
306 dst,
307 in_total,
308 out_dims,
309 in_strides,
310 elem_bytes,
311 },
312 }
313 }
314}
315
316#[allow(unused_variables)]
317pub(crate) fn compile_transpose(
318 node: &rlx_ir::Node,
319 graph: &Graph,
320 arena: &crate::arena::Arena,
321 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
322 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
323 rng: rlx_ir::RngOptions,
324) -> Thunk {
325 let Op::Transpose { perm } = &node.op else {
326 unreachable!()
327 };
328 {
329 let in_shape = &graph.node(node.inputs[0]).shape;
332 let in_rank = in_shape.rank();
333 if perm.iter().any(|&p| p >= in_rank) {
334 Thunk::Nop
335 } else {
336 let in_dims: Vec<usize> = (0..in_rank)
337 .map(|i| in_shape.dim(i).unwrap_static())
338 .collect();
339 let mut in_strides_full = vec![1usize; in_rank];
341 for d in (0..in_rank.saturating_sub(1)).rev() {
342 in_strides_full[d] = in_strides_full[d + 1] * in_dims[d + 1];
343 }
344 let out_dims: Vec<u32> = perm.iter().map(|&p| in_dims[p] as u32).collect();
345 let in_strides: Vec<u32> = perm.iter().map(|&p| in_strides_full[p] as u32).collect();
346 let in_total = in_dims.iter().product::<usize>() as u32;
347 let src = node_offset(arena, node.inputs[0]);
348 let dst = node_offset(arena, node.id);
349 let elem_bytes = node.shape.dtype().size_bytes() as u8;
350 match node.shape.dtype() {
351 rlx_ir::DType::F64 => Thunk::TransposeF64 {
352 src,
353 dst,
354 in_total,
355 out_dims,
356 in_strides,
357 },
358 _ => Thunk::Transpose {
359 src,
360 dst,
361 in_total,
362 out_dims,
363 in_strides,
364 elem_bytes,
365 },
366 }
367 }
368 }
369}
370
371#[allow(unused_variables)]
372pub(crate) fn compile_scatter_add(
373 node: &rlx_ir::Node,
374 graph: &Graph,
375 arena: &crate::arena::Arena,
376 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
377 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
378 rng: rlx_ir::RngOptions,
379) -> Thunk {
380 let Op::ScatterAdd = &node.op else {
381 unreachable!()
382 };
383 {
384 let upd_shape = &graph.node(node.inputs[0]).shape;
387 let out_shape = &node.shape;
388 let num_updates = upd_shape.dim(0).unwrap_static();
389 let out_dim = out_shape.dim(0).unwrap_static();
390 let trailing: usize = (1..out_shape.rank())
391 .map(|i| out_shape.dim(i).unwrap_static())
392 .product::<usize>()
393 .max(1);
394 Thunk::ScatterAdd {
395 updates: node_offset(arena, node.inputs[0]),
396 indices: node_offset(arena, node.inputs[1]),
397 dst: node_offset(arena, node.id),
398 num_updates: num_updates as u32,
399 out_dim: out_dim as u32,
400 trailing: trailing as u32,
401 }
402 }
403}
404
405#[allow(unused_variables)]
406pub(crate) fn compile_scatter_nd(
407 node: &rlx_ir::Node,
408 graph: &Graph,
409 arena: &crate::arena::Arena,
410 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
411 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
412 rng: rlx_ir::RngOptions,
413) -> Thunk {
414 let Op::ScatterNd { reduction } = &node.op else {
415 unreachable!()
416 };
417 let data_shape = &graph.node(node.inputs[0]).shape;
418 let indices_shape = &graph.node(node.inputs[1]).shape;
419 let updates_shape = &graph.node(node.inputs[2]).shape;
420 let data_len = data_shape.num_elements().unwrap_or(0);
421 let updates_len = updates_shape.num_elements().unwrap_or(0);
422 let indices_len = indices_shape.num_elements().unwrap_or(0);
423 let indices_i64 = u8::from(indices_shape.dtype() == rlx_ir::DType::I64);
424 Thunk::ScatterNd {
425 data: node_offset(arena, node.inputs[0]),
426 indices: node_offset(arena, node.inputs[1]),
427 updates: node_offset(arena, node.inputs[2]),
428 dst: node_offset(arena, node.id),
429 data_shape: (0..data_shape.rank())
430 .map(|i| data_shape.dim(i).unwrap_static() as u32)
431 .collect(),
432 indices_shape: (0..indices_shape.rank())
433 .map(|i| indices_shape.dim(i).unwrap_static() as u32)
434 .collect(),
435 data_len: data_len as u32,
436 updates_len: updates_len as u32,
437 indices_len: indices_len as u32,
438 indices_i64,
439 reduction: *reduction,
440 }
441}
442
443fn shape_u32s(shape: &rlx_ir::Shape) -> Vec<u32> {
444 (0..shape.rank())
445 .map(|i| shape.dim(i).unwrap_static() as u32)
446 .collect()
447}
448
449#[allow(unused_variables)]
450pub(crate) fn compile_scatter_elements(
451 node: &rlx_ir::Node,
452 graph: &Graph,
453 arena: &crate::arena::Arena,
454 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
455 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
456 rng: rlx_ir::RngOptions,
457) -> Thunk {
458 let Op::ScatterElements { axis, reduction } = &node.op else {
459 unreachable!()
460 };
461 let data_shape = &graph.node(node.inputs[0]).shape;
462 let indices_shape = &graph.node(node.inputs[1]).shape;
463 let updates_shape = &graph.node(node.inputs[2]).shape;
464 Thunk::ScatterElements {
465 data: node_offset(arena, node.inputs[0]),
466 indices: node_offset(arena, node.inputs[1]),
467 updates: node_offset(arena, node.inputs[2]),
468 dst: node_offset(arena, node.id),
469 data_shape: shape_u32s(data_shape),
470 data_len: data_shape.num_elements().unwrap_or(0) as u32,
471 updates_len: updates_shape.num_elements().unwrap_or(0) as u32,
472 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
473 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
474 axis: *axis,
475 reduction: *reduction,
476 }
477}
478
479#[allow(unused_variables)]
480pub(crate) fn compile_gather_nd(
481 node: &rlx_ir::Node,
482 graph: &Graph,
483 arena: &crate::arena::Arena,
484 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
485 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
486 rng: rlx_ir::RngOptions,
487) -> Thunk {
488 let Op::GatherNd { batch_dims } = &node.op else {
489 unreachable!()
490 };
491 let data_shape = &graph.node(node.inputs[0]).shape;
492 let indices_shape = &graph.node(node.inputs[1]).shape;
493 Thunk::GatherNd {
494 data: node_offset(arena, node.inputs[0]),
495 indices: node_offset(arena, node.inputs[1]),
496 dst: node_offset(arena, node.id),
497 data_shape: shape_u32s(data_shape),
498 indices_shape: shape_u32s(indices_shape),
499 data_len: data_shape.num_elements().unwrap_or(0) as u32,
500 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
501 out_len: node.shape.num_elements().unwrap_or(0) as u32,
502 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
503 batch_dims: *batch_dims,
504 }
505}
506
507#[allow(unused_variables)]
508pub(crate) fn compile_gather_elements(
509 node: &rlx_ir::Node,
510 graph: &Graph,
511 arena: &crate::arena::Arena,
512 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
513 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
514 rng: rlx_ir::RngOptions,
515) -> Thunk {
516 let Op::GatherElements { axis } = &node.op else {
517 unreachable!()
518 };
519 let data_shape = &graph.node(node.inputs[0]).shape;
520 let indices_shape = &graph.node(node.inputs[1]).shape;
521 Thunk::GatherElements {
522 data: node_offset(arena, node.inputs[0]),
523 indices: node_offset(arena, node.inputs[1]),
524 dst: node_offset(arena, node.id),
525 data_shape: shape_u32s(data_shape),
526 indices_shape: shape_u32s(indices_shape),
527 data_len: data_shape.num_elements().unwrap_or(0) as u32,
528 indices_len: indices_shape.num_elements().unwrap_or(0) as u32,
529 out_len: node.shape.num_elements().unwrap_or(0) as u32,
530 indices_i64: u8::from(indices_shape.dtype() == rlx_ir::DType::I64),
531 axis: *axis,
532 }
533}
534
535#[allow(unused_variables)]
536pub(crate) fn compile_compare(
537 node: &rlx_ir::Node,
538 graph: &Graph,
539 arena: &crate::arena::Arena,
540 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
541 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
542 rng: rlx_ir::RngOptions,
543) -> Thunk {
544 let Op::Compare(cmp) = &node.op else {
545 unreachable!()
546 };
547 {
548 let len = node.shape.num_elements().unwrap();
549 let lhs_n = graph.node(node.inputs[0]).shape.num_elements().unwrap();
550 let rhs_n = graph.node(node.inputs[1]).shape.num_elements().unwrap();
551 let lhs_scalar = lhs_n == 1 && len > 1;
552 let rhs_scalar = rhs_n == 1 && len > 1;
553 let in_dtype = graph.node(node.inputs[0]).shape.dtype();
554 let inputs_i64 = u8::from(in_dtype == rlx_ir::DType::I64);
555 Thunk::Compare {
556 lhs: node_offset(arena, node.inputs[0]),
557 rhs: node_offset(arena, node.inputs[1]),
558 dst: node_offset(arena, node.id),
559 len: len as u32,
560 op: *cmp,
561 inputs_i64,
562 inputs_elem_bytes: in_dtype.size_bytes() as u8,
563 dst_elem_bytes: node.shape.dtype().size_bytes() as u8,
564 lhs_scalar,
565 rhs_scalar,
566 }
567 }
568}
569
570#[allow(unused_variables)]
571pub(crate) fn compile_where(
572 node: &rlx_ir::Node,
573 graph: &Graph,
574 arena: &crate::arena::Arena,
575 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
576 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
577 rng: rlx_ir::RngOptions,
578) -> Thunk {
579 let Op::Where = &node.op else { unreachable!() };
580 {
581 let len = node.shape.num_elements().unwrap();
582 let cn = graph.node(node.inputs[0]).shape.num_elements().unwrap();
583 let tn = graph.node(node.inputs[1]).shape.num_elements().unwrap();
584 let fn_ = graph.node(node.inputs[2]).shape.num_elements().unwrap();
585 let cond_scalar = cn == 1 && len > 1;
586 let true_scalar = tn == 1 && len > 1;
587 let false_scalar = fn_ == 1 && len > 1;
588 let elem_bytes = node.shape.dtype().size_bytes() as u8;
589 let cond_elem_bytes = graph.node(node.inputs[0]).shape.dtype().size_bytes() as u8;
590 Thunk::Where {
591 cond: node_offset(arena, node.inputs[0]),
592 on_true: node_offset(arena, node.inputs[1]),
593 on_false: node_offset(arena, node.inputs[2]),
594 dst: node_offset(arena, node.id),
595 len: len as u32,
596 elem_bytes,
597 cond_elem_bytes,
598 cond_scalar,
599 true_scalar,
600 false_scalar,
601 }
602 }
603}
604
605#[allow(unused_variables)]
606pub(crate) fn compile_fma(
607 node: &rlx_ir::Node,
608 graph: &Graph,
609 arena: &crate::arena::Arena,
610 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
611 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
612 rng: rlx_ir::RngOptions,
613) -> Thunk {
614 let Op::Fma = &node.op else { unreachable!() };
615 {
616 let len = node.shape.num_elements().unwrap();
617 Thunk::Fma {
618 a: node_offset(arena, node.inputs[0]),
619 b: node_offset(arena, node.inputs[1]),
620 c: node_offset(arena, node.inputs[2]),
621 dst: node_offset(arena, node.id),
622 len: len as u32,
623 elem_bytes: node.shape.dtype().size_bytes() as u8,
624 }
625 }
626}
627
628#[allow(unused_variables)]
629pub(crate) fn compile_relu_backward(
630 node: &rlx_ir::Node,
631 graph: &Graph,
632 arena: &crate::arena::Arena,
633 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
634 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
635 rng: rlx_ir::RngOptions,
636) -> Thunk {
637 let Op::ReluBackward = &node.op else {
638 unreachable!()
639 };
640 {
641 let len: usize = (0..node.shape.rank())
642 .map(|i| node.shape.dim(i).unwrap_static())
643 .product();
644 let x = node_offset(arena, node.inputs[0]);
645 let dy = node_offset(arena, node.inputs[1]);
646 let dx = node_offset(arena, node.id);
647 match node.shape.dtype() {
648 rlx_ir::DType::F64 => Thunk::ReluBackwardF64 {
649 x,
650 dy,
651 dx,
652 len: len as u32,
653 },
654 _ => Thunk::ReluBackward {
655 x,
656 dy,
657 dx,
658 len: len as u32,
659 },
660 }
661 }
662}
663
664#[allow(unused_variables)]
665pub(crate) fn compile_activation_backward(
666 node: &rlx_ir::Node,
667 graph: &Graph,
668 arena: &crate::arena::Arena,
669 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
670 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
671 rng: rlx_ir::RngOptions,
672) -> Thunk {
673 let Op::ActivationBackward { kind } = &node.op else {
674 unreachable!()
675 };
676 {
677 let len: usize = (0..node.shape.rank())
678 .map(|i| node.shape.dim(i).unwrap_static())
679 .product();
680 let x = node_offset(arena, node.inputs[0]);
681 let dy = node_offset(arena, node.inputs[1]);
682 let dx = node_offset(arena, node.id);
683 match node.shape.dtype() {
684 rlx_ir::DType::F64 => Thunk::ActivationBackwardF64 {
685 x,
686 dy,
687 dx,
688 len: len as u32,
689 kind: *kind,
690 },
691 _ => Thunk::ActivationBackward {
692 x,
693 dy,
694 dx,
695 len: len as u32,
696 kind: *kind,
697 },
698 }
699 }
700}
701
702#[allow(unused_variables)]
703pub(crate) fn compile_gather_backward(
704 node: &rlx_ir::Node,
705 graph: &Graph,
706 arena: &crate::arena::Arena,
707 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
708 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
709 rng: rlx_ir::RngOptions,
710) -> Thunk {
711 let Op::GatherBackward { .. } = &node.op else {
712 unreachable!()
713 };
714 {
715 let dy_shape = &graph.node(node.inputs[0]).shape;
716 let idx_shape = &graph.node(node.inputs[1]).shape;
717 let out_shape = &node.shape;
718 let rank = out_shape.rank();
719 let axis = match &node.op {
720 Op::GatherBackward { axis } => *axis,
721 _ => 0,
722 };
723 let axis_u = if axis < 0 {
724 (rank as i32 + axis) as usize
725 } else {
726 axis as usize
727 };
728 let outer: usize = (0..axis_u)
729 .map(|i| dy_shape.dim(i).unwrap_static())
730 .product::<usize>()
731 .max(1);
732 let num_idx = idx_shape.dim(axis_u).unwrap_static();
733 let trailing: usize = (axis_u + 1..dy_shape.rank())
734 .map(|i| dy_shape.dim(i).unwrap_static())
735 .product::<usize>()
736 .max(1);
737 let axis_dim = out_shape.dim(axis_u).unwrap_static();
738 Thunk::GatherBackward {
739 dy: node_offset(arena, node.inputs[0]),
740 indices: node_offset(arena, node.inputs[1]),
741 dst: node_offset(arena, node.id),
742 outer: outer as u32,
743 axis_dim: axis_dim as u32,
744 num_idx: num_idx as u32,
745 trailing: trailing as u32,
746 }
747 }
748}
749
750#[allow(unused_variables)]
751pub(crate) fn compile_concat(
752 node: &rlx_ir::Node,
753 graph: &Graph,
754 arena: &crate::arena::Arena,
755 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
756 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
757 rng: rlx_ir::RngOptions,
758) -> Thunk {
759 let Op::Concat { axis } = &node.op else {
760 unreachable!()
761 };
762 {
763 let out_shape = &node.shape;
767 let rank = out_shape.rank();
768 let outer: usize = (0..*axis)
769 .map(|i| out_shape.dim(i).unwrap_static())
770 .product::<usize>()
771 .max(1);
772 let inner: usize = (*axis + 1..rank)
773 .map(|i| out_shape.dim(i).unwrap_static())
774 .product::<usize>()
775 .max(1);
776 let total_axis = out_shape.dim(*axis).unwrap_static();
777 let inputs: Vec<(usize, u32, u32)> = node
778 .inputs
779 .iter()
780 .map(|&in_id| {
781 let in_shape = &graph.node(in_id).shape;
782 let in_axis = concat_axis_extent(in_shape, *axis, rank);
783 let in_numel = in_shape.num_elements().unwrap_or(0) as u32;
784 (node_offset(arena, in_id), in_axis as u32, in_numel)
785 })
786 .collect();
787 let dst = node_offset(arena, node.id);
788 match out_shape.dtype().size_bytes() {
796 8 => Thunk::ConcatF64 {
797 dst,
798 outer: outer as u32,
799 inner: inner as u32,
800 total_axis: total_axis as u32,
801 inputs,
802 },
803 _ => Thunk::Concat {
804 dst,
805 outer: outer as u32,
806 inner: inner as u32,
807 total_axis: total_axis as u32,
808 inputs,
809 },
810 }
811 }
812}
813
814#[allow(unused_variables)]
815pub(crate) fn compile_elementwise_region(
816 node: &rlx_ir::Node,
817 graph: &Graph,
818 arena: &crate::arena::Arena,
819 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
820 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
821 rng: rlx_ir::RngOptions,
822) -> Thunk {
823 let Op::ElementwiseRegion {
824 chain,
825 scalar_input_mask,
826 input_modulus,
827 prologue,
828 ..
829 } = &node.op
830 else {
831 unreachable!()
832 };
833 {
834 if *prologue != rlx_ir::op::RegionPrologue::None {
838 Thunk::Nop
839 } else {
840 let input_offs: Vec<usize> = node
841 .inputs
842 .iter()
843 .map(|&id| node_offset(arena, id))
844 .collect();
845 Thunk::ElementwiseRegion {
846 dst: node_offset(arena, node.id),
847 len: node.shape.num_elements().unwrap_or(0) as u32,
848 input_offs,
849 chain: chain.clone(),
850 scalar_input_mask: *scalar_input_mask,
851 input_modulus: *input_modulus,
852 }
853 }
854 }
855}
856
857pub(crate) fn get_len(graph: &Graph, id: NodeId) -> usize {
858 graph.node(id).shape.num_elements().unwrap_or(0)
859}
860
861pub(crate) fn get_static_dims(graph: &Graph, id: NodeId) -> Vec<usize> {
863 let dims = graph.node(id).shape.dims();
864 let mut out = Vec::with_capacity(dims.len());
865 for d in dims {
866 if let Some(s) = match d {
867 rlx_ir::Dim::Static(s) => Some(*s),
868 _ => None,
869 } {
870 out.push(s);
871 } else {
872 return Vec::new();
873 }
874 }
875 out
876}
877
878pub(crate) fn concat_axis_extent(input: &rlx_ir::Shape, axis: usize, out_rank: usize) -> usize {
881 let in_rank = input.rank();
882 if axis >= out_rank {
883 return 1;
884 }
885 if axis < in_rank {
886 input.dim(axis).unwrap_static()
887 } else {
888 1
889 }
890}
891
892pub(crate) fn broadcast_src_index(src_idx: usize, in_len: usize) -> usize {
893 if in_len == 0 { 0 } else { src_idx % in_len }
894}
895
896macro_rules! concat_copy_rows {
900 ($name:ident, $t:ty) => {
901 pub(crate) fn $name(
902 out: &mut [$t],
903 inp: &[$t],
904 outer: usize,
905 copy_per_row: usize,
906 row_stride: usize,
907 dst_col_off: usize,
908 in_numel: usize,
909 ) {
910 let need = outer.saturating_mul(copy_per_row.max(1));
911 let broadcast_outer = in_numel < need;
912 let out_ptr = out.as_mut_ptr() as usize;
913 let inp_ptr = inp.as_ptr() as usize;
914 let inp_len = inp.len();
915 let run = |o0: usize, o1: usize| {
916 let out = unsafe { std::slice::from_raw_parts_mut(out_ptr as *mut $t, out.len()) };
917 let inp = unsafe { std::slice::from_raw_parts(inp_ptr as *const $t, inp_len) };
918 for o in o0..o1 {
919 let dst_row_start = o * row_stride + dst_col_off;
920 if broadcast_outer {
921 if in_numel == 1 {
922 if copy_per_row == 1 {
923 out[dst_row_start] = inp[0];
924 } else {
925 out[dst_row_start..dst_row_start + copy_per_row].fill(inp[0]);
926 }
927 } else if copy_per_row <= inp.len() {
928 out[dst_row_start..dst_row_start + copy_per_row]
929 .copy_from_slice(&inp[..copy_per_row]);
930 } else if !inp.is_empty() {
931 out[dst_row_start..dst_row_start + copy_per_row].fill(inp[0]);
932 }
933 } else {
934 let src_row_start = o * copy_per_row;
935 out[dst_row_start..dst_row_start + copy_per_row]
936 .copy_from_slice(&inp[src_row_start..src_row_start + copy_per_row]);
937 }
938 }
939 };
940 if outer >= 8
942 && copy_per_row >= 16
943 && crate::pool::num_threads() > 1
944 && crate::pool::should_parallelize(outer.saturating_mul(copy_per_row))
945 {
946 crate::pool::par_for(outer, 1, &|off, cnt| run(off, off + cnt));
947 } else {
948 run(0, outer);
949 }
950 }
951 };
952}
953
954concat_copy_rows!(concat_copy_rows_f32, f32);
955concat_copy_rows!(concat_copy_rows_f64, f64);
956
957pub(crate) fn is_trailing_bias_broadcast(
975 rhs_dims: &[rlx_ir::Dim],
976 out_dims: &[rlx_ir::Dim],
977) -> bool {
978 if rhs_dims.len() > out_dims.len() {
979 return false;
980 }
981 let off = out_dims.len() - rhs_dims.len();
982 for i in 0..rhs_dims.len() {
983 let r = match rhs_dims[i] {
984 rlx_ir::Dim::Static(n) => n,
985 _ => return false,
986 };
987 let o = match out_dims[off + i] {
988 rlx_ir::Dim::Static(n) => n,
989 _ => return false,
990 };
991 if r != o {
992 return false;
993 }
994 }
995 true
996}
997
998pub(crate) fn broadcast_strides(in_dims: &[usize], out_dims: &[usize]) -> Vec<u32> {
999 let r_out = out_dims.len();
1000 let r_in = in_dims.len();
1001 assert!(
1002 r_in <= r_out,
1003 "broadcast: input rank {r_in} > output rank {r_out}"
1004 );
1005 let pad = r_out - r_in;
1006 let mut strides = vec![0u32; r_out];
1007 let mut acc: usize = 1;
1008 for d in (0..r_out).rev() {
1009 let in_size = if d < pad { 1 } else { in_dims[d - pad] };
1010 if in_size == 1 {
1011 strides[d] = 0;
1012 } else {
1013 assert_eq!(
1014 in_size, out_dims[d],
1015 "broadcast: input dim {in_size} doesn't match output dim {} at axis {d}",
1016 out_dims[d]
1017 );
1018 strides[d] = acc as u32;
1019 acc *= in_size;
1020 }
1021 }
1022 strides
1023}
1024
1025#[inline(always)]
1026pub(crate) fn exec_elementwise_region(t: &Thunk, base: *mut u8) {
1027 let Thunk::ElementwiseRegion {
1028 dst,
1029 len,
1030 input_offs,
1031 chain,
1032 scalar_input_mask,
1033 input_modulus,
1034 } = t
1035 else {
1036 unreachable!()
1037 };
1038 {
1039 let len = *len as usize;
1040 if !chain.is_empty() && len > 0 {
1041 let base_addr = base as usize;
1042 let dst = *dst;
1043 let scalar_mask = *scalar_input_mask;
1044 let eval = |gid: usize| {
1046 let v = region_eval_elem(
1047 gid,
1048 base_addr as *const u8,
1049 input_offs,
1050 chain,
1051 scalar_mask,
1052 input_modulus,
1053 );
1054 unsafe {
1055 *((base_addr as *mut u8).add(dst) as *mut f32).add(gid) = v;
1056 }
1057 };
1058 if fast_conv_enabled() && crate::pool::should_parallelize(len) {
1059 crate::pool::par_for(len, crate::pool::chunk_floor(len), &|off, cnt| {
1060 for gid in off..off + cnt {
1061 eval(gid);
1062 }
1063 });
1064 } else {
1065 for gid in 0..len {
1066 eval(gid);
1067 }
1068 }
1069 }
1070 }
1071}
1072
1073#[inline(always)]
1074pub(crate) fn exec_transpose_f64(t: &Thunk, base: *mut u8) {
1075 let Thunk::TransposeF64 {
1076 src,
1077 dst,
1078 in_total,
1079 out_dims,
1080 in_strides,
1081 } = t
1082 else {
1083 unreachable!()
1084 };
1085 unsafe {
1086 let inp = sl_f64(*src, base, *in_total as usize);
1087 let out_total: usize = out_dims.iter().map(|d| *d as usize).product();
1088 let out = sl_mut_f64(*dst, base, out_total);
1089 transpose_walk_f64(inp, out, out_dims, in_strides);
1090 }
1091}
1092
1093#[inline(always)]
1094pub(crate) fn exec_activation_f64(t: &Thunk, base: *mut u8) {
1095 let Thunk::ActivationF64 {
1096 src,
1097 dst,
1098 len,
1099 kind,
1100 } = t
1101 else {
1102 unreachable!()
1103 };
1104 {
1105 let len = *len as usize;
1106 unsafe {
1107 let inp = sl_f64(*src, base, len);
1108 let out = sl_mut_f64(*dst, base, len);
1109 apply_activation_f64(inp, out, *kind);
1110 }
1111 }
1112}
1113
1114#[inline(always)]
1115pub(crate) fn exec_binary_full_f64(t: &Thunk, base: *mut u8) {
1116 let Thunk::BinaryFullF64 {
1117 lhs,
1118 rhs,
1119 dst,
1120 len,
1121 lhs_len,
1122 rhs_len,
1123 op,
1124 out_dims_bcast,
1125 bcast_lhs_strides,
1126 bcast_rhs_strides,
1127 } = t
1128 else {
1129 unreachable!()
1130 };
1131 {
1132 let len = *len as usize;
1133 let lhs_len = *lhs_len as usize;
1134 let rhs_len = *rhs_len as usize;
1135 unsafe {
1136 let l = sl_f64(*lhs, base, lhs_len);
1137 let r = sl_f64(*rhs, base, rhs_len);
1138 let d = sl_mut_f64(*dst, base, len);
1139 if lhs_len == len && rhs_len == len {
1140 for i in 0..len {
1141 d[i] = binary_op_f64(*op, l[i], r[i]);
1142 }
1143 } else if !out_dims_bcast.is_empty() {
1144 let rank = out_dims_bcast.len();
1148 let mut coords = vec![0u32; rank];
1149 for i in 0..len {
1150 let mut rem = i;
1151 for ax in (0..rank).rev() {
1152 let sz = out_dims_bcast[ax] as usize;
1153 coords[ax] = (rem % sz) as u32;
1154 rem /= sz;
1155 }
1156 let mut li: usize = 0;
1157 let mut ri: usize = 0;
1158 for ax in 0..rank {
1159 li += coords[ax] as usize * bcast_lhs_strides[ax] as usize;
1160 ri += coords[ax] as usize * bcast_rhs_strides[ax] as usize;
1161 }
1162 d[i] = binary_op_f64(*op, l[li], r[ri]);
1163 }
1164 } else {
1165 for i in 0..len {
1170 d[i] = binary_op_f64(*op, l[i % lhs_len], r[i % rhs_len]);
1171 }
1172 }
1173 }
1174 }
1175}
1176
1177#[inline(always)]
1178pub(crate) fn exec_binary_full_c64(t: &Thunk, base: *mut u8) {
1179 let Thunk::BinaryFullC64 {
1180 lhs,
1181 rhs,
1182 dst,
1183 len,
1184 lhs_len,
1185 rhs_len,
1186 op,
1187 out_dims_bcast,
1188 bcast_lhs_strides,
1189 bcast_rhs_strides,
1190 } = t
1191 else {
1192 unreachable!()
1193 };
1194 {
1195 let n_out = *len as usize;
1201 let n_l = *lhs_len as usize;
1202 let n_r = *rhs_len as usize;
1203 unsafe {
1204 let l = sl(*lhs, base, 2 * n_l);
1205 let r = sl(*rhs, base, 2 * n_r);
1206 let d = sl_mut(*dst, base, 2 * n_out);
1207 let do_c64 = |a_re: f32, a_im: f32, b_re: f32, b_im: f32| -> (f32, f32) {
1208 match op {
1209 BinaryOp::Add => (a_re + b_re, a_im + b_im),
1210 BinaryOp::Sub => (a_re - b_re, a_im - b_im),
1211 BinaryOp::Mul => (a_re * b_re - a_im * b_im, a_re * b_im + a_im * b_re),
1212 BinaryOp::Div => {
1213 let denom = b_re * b_re + b_im * b_im;
1214 (
1215 (a_re * b_re + a_im * b_im) / denom,
1216 (a_im * b_re - a_re * b_im) / denom,
1217 )
1218 }
1219 BinaryOp::Max | BinaryOp::Min | BinaryOp::Pow => {
1220 unreachable!("C64 max/min/pow rejected at lowering")
1221 }
1222 }
1223 };
1224 if n_l == n_out && n_r == n_out {
1225 for i in 0..n_out {
1226 let (re, im) = do_c64(l[2 * i], l[2 * i + 1], r[2 * i], r[2 * i + 1]);
1227 d[2 * i] = re;
1228 d[2 * i + 1] = im;
1229 }
1230 } else if !out_dims_bcast.is_empty() {
1231 let rank = out_dims_bcast.len();
1235 let mut coords = vec![0u32; rank];
1236 for i in 0..n_out {
1237 let mut rem = i;
1238 for ax in (0..rank).rev() {
1239 let sz = out_dims_bcast[ax] as usize;
1240 coords[ax] = (rem % sz) as u32;
1241 rem /= sz;
1242 }
1243 let mut li: usize = 0;
1244 let mut ri: usize = 0;
1245 for ax in 0..rank {
1246 li += coords[ax] as usize * bcast_lhs_strides[ax] as usize;
1247 ri += coords[ax] as usize * bcast_rhs_strides[ax] as usize;
1248 }
1249 let (re, im) = do_c64(l[2 * li], l[2 * li + 1], r[2 * ri], r[2 * ri + 1]);
1250 d[2 * i] = re;
1251 d[2 * i + 1] = im;
1252 }
1253 } else {
1254 for i in 0..n_out {
1256 let li = if n_l == 1 { 0 } else { i % n_l };
1257 let ri = if n_r == 1 { 0 } else { i % n_r };
1258 let (re, im) = do_c64(l[2 * li], l[2 * li + 1], r[2 * ri], r[2 * ri + 1]);
1259 d[2 * i] = re;
1260 d[2 * i + 1] = im;
1261 }
1262 }
1263 }
1264 }
1265}
1266
1267#[inline(always)]
1268pub(crate) fn exec_activation_c64(t: &Thunk, base: *mut u8) {
1269 let Thunk::ActivationC64 {
1270 src,
1271 dst,
1272 len,
1273 kind,
1274 } = t
1275 else {
1276 unreachable!()
1277 };
1278 {
1279 let n = *len as usize;
1280 unsafe {
1281 let s = sl(*src, base, 2 * n);
1282 let d = sl_mut(*dst, base, 2 * n);
1283 for i in 0..n {
1284 let a = s[2 * i];
1285 let b = s[2 * i + 1];
1286 let (re, im) = match kind {
1287 Activation::Neg => (-a, -b),
1288 Activation::Exp => {
1289 let ea = a.exp();
1291 (ea * b.cos(), ea * b.sin())
1292 }
1293 Activation::Log => {
1294 let r = (a * a + b * b).sqrt();
1296 (r.ln(), b.atan2(a))
1297 }
1298 Activation::Sqrt => {
1299 let r = (a * a + b * b).sqrt();
1302 let re = ((r + a) * 0.5).max(0.0).sqrt();
1303 let im_mag = ((r - a) * 0.5).max(0.0).sqrt();
1304 let im = if b >= 0.0 { im_mag } else { -im_mag };
1305 (re, im)
1306 }
1307 _ => unreachable!("non-C64 activation kind survived lowering"),
1308 };
1309 d[2 * i] = re;
1310 d[2 * i + 1] = im;
1311 }
1312 }
1313 }
1314}
1315
1316#[inline(always)]
1317pub(crate) fn exec_gather(t: &Thunk, base: *mut u8) {
1318 let Thunk::Gather {
1319 table,
1320 table_len,
1321 idx,
1322 dst,
1323 num_idx,
1324 trailing,
1325 idx_i64,
1326 table_bytes,
1327 } = t
1328 else {
1329 unreachable!()
1330 };
1331 {
1332 let (ni, tr) = (*num_idx as usize, *trailing as usize);
1333 let rows = *table_len as usize / tr.max(1);
1334 unsafe {
1335 if *table_bytes == 8 {
1336 let tab = sl_i64(*table, base, *table_len as usize);
1337 let out = sl_mut_i64(*dst, base, ni * tr);
1338 if *idx_i64 != 0 {
1339 let ids = sl_i64(*idx, base, ni);
1340 for i in 0..ni {
1341 let row = ids[i].max(0) as usize;
1342 if row < rows {
1343 out[i * tr..(i + 1) * tr]
1344 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1345 }
1346 }
1347 } else {
1348 let ids = sl(*idx, base, ni);
1349 for i in 0..ni {
1350 let row = ids[i] as usize;
1351 if row < rows {
1352 out[i * tr..(i + 1) * tr]
1353 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1354 }
1355 }
1356 }
1357 } else {
1358 let tab = sl(*table, base, *table_len as usize);
1359 let out = sl_mut(*dst, base, ni * tr);
1360 if *idx_i64 != 0 {
1361 let ids = sl_i64(*idx, base, ni);
1362 for i in 0..ni {
1363 let row = ids[i].max(0) as usize;
1364 if row < rows {
1365 out[i * tr..(i + 1) * tr]
1366 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1367 }
1368 }
1369 } else {
1370 let ids = sl(*idx, base, ni);
1371 for i in 0..ni {
1372 let row = ids[i] as usize;
1373 if row < rows {
1374 out[i * tr..(i + 1) * tr]
1375 .copy_from_slice(&tab[row * tr..(row + 1) * tr]);
1376 }
1377 }
1378 }
1379 }
1380 }
1381 }
1382}
1383
1384#[inline(always)]
1385pub(crate) fn exec_activation_in_place(t: &Thunk, base: *mut u8) {
1386 let Thunk::ActivationInPlace { data, len, act } = t else {
1387 unreachable!()
1388 };
1389 {
1390 let len = *len as usize;
1391 unsafe {
1392 let d = sl_mut(*data, base, len);
1393 let par = crate::pool::should_parallelize(len);
1399 macro_rules! apply {
1400 ($f:expr) => {{
1401 let f = $f;
1402 if par {
1403 use rayon::prelude::*;
1404 d.par_iter_mut().for_each(|v| *v = f(*v));
1405 } else {
1406 for v in d.iter_mut() {
1407 *v = f(*v);
1408 }
1409 }
1410 }};
1411 }
1412 match act {
1413 Activation::Gelu => crate::kernels::par_gelu_inplace(d),
1414 Activation::GeluApprox => crate::kernels::par_gelu_approx_inplace(d),
1415 Activation::Silu => crate::kernels::par_silu_inplace(d),
1416 Activation::Relu => apply!(|x: f32| x.max(0.0)),
1417 Activation::Sigmoid => apply!(|x: f32| 1.0 / (1.0 + (-x).exp())),
1418 Activation::Tanh => apply!(|x: f32| x.tanh()),
1419 Activation::Exp => apply!(|x: f32| x.exp()),
1420 Activation::Log => apply!(|x: f32| x.ln()),
1421 Activation::Sqrt => apply!(|x: f32| x.sqrt()),
1422 Activation::Rsqrt => apply!(|x: f32| 1.0 / x.sqrt()),
1423 Activation::Neg => apply!(|x: f32| -x),
1424 Activation::Abs => apply!(|x: f32| x.abs()),
1425 Activation::Round => apply!(|x: f32| x.round()),
1426 Activation::Sin => apply!(|x: f32| x.sin()),
1427 Activation::Cos => apply!(|x: f32| x.cos()),
1428 Activation::Tan => apply!(|x: f32| x.tan()),
1429 Activation::Atan => apply!(|x: f32| x.atan()),
1430 }
1431 }
1432 }
1433}
1434
1435#[inline(always)]
1436pub(crate) fn exec_concat(t: &Thunk, base: *mut u8) {
1437 let Thunk::Concat {
1438 dst,
1439 outer,
1440 inner,
1441 total_axis,
1442 inputs,
1443 } = t
1444 else {
1445 unreachable!()
1446 };
1447 {
1448 let outer = *outer as usize;
1449 let inner = *inner as usize;
1450 let total_axis = *total_axis as usize;
1451 let row_stride = total_axis * inner;
1452 let out_total = outer * row_stride;
1453 unsafe {
1454 let out = sl_mut(*dst, base, out_total);
1455 let mut cum: usize = 0;
1456 for (src_off, in_axis, in_numel) in inputs {
1457 let in_axis = *in_axis as usize;
1458 let copy_per_row = in_axis * inner;
1459 let dst_col_off = cum * inner;
1460 let inp = sl(*src_off, base, (*in_numel as usize).max(1));
1461 concat_copy_rows_f32(
1462 out,
1463 inp,
1464 outer,
1465 copy_per_row,
1466 row_stride,
1467 dst_col_off,
1468 *in_numel as usize,
1469 );
1470 cum += in_axis;
1471 }
1472 }
1473 }
1474}
1475
1476#[inline(always)]
1477pub(crate) fn exec_concat_f64(t: &Thunk, base: *mut u8) {
1478 let Thunk::ConcatF64 {
1479 dst,
1480 outer,
1481 inner,
1482 total_axis,
1483 inputs,
1484 } = t
1485 else {
1486 unreachable!()
1487 };
1488 {
1489 let outer = *outer as usize;
1490 let inner = *inner as usize;
1491 let total_axis = *total_axis as usize;
1492 let row_stride = total_axis * inner;
1493 let out_total = outer * row_stride;
1494 unsafe {
1495 let out = sl_mut_f64(*dst, base, out_total);
1496 let mut cum: usize = 0;
1497 for (src_off, in_axis, in_numel) in inputs {
1498 let in_axis = *in_axis as usize;
1499 let copy_per_row = in_axis * inner;
1500 let dst_col_off = cum * inner;
1501 let inp = sl_f64(*src_off, base, (*in_numel as usize).max(1));
1502 concat_copy_rows_f64(
1503 out,
1504 inp,
1505 outer,
1506 copy_per_row,
1507 row_stride,
1508 dst_col_off,
1509 *in_numel as usize,
1510 );
1511 cum += in_axis;
1512 }
1513 }
1514 }
1515}
1516
1517#[inline(always)]
1518pub(crate) fn exec_scatter_add(t: &Thunk, base: *mut u8) {
1519 let Thunk::ScatterAdd {
1520 updates,
1521 indices,
1522 dst,
1523 num_updates,
1524 out_dim,
1525 trailing,
1526 } = t
1527 else {
1528 unreachable!()
1529 };
1530 {
1531 let num_updates = *num_updates as usize;
1532 let out_dim = *out_dim as usize;
1533 let trailing = *trailing as usize;
1534 unsafe {
1535 let upd = sl(*updates, base, num_updates * trailing);
1536 let ids = sl(*indices, base, num_updates);
1537 let out = sl_mut(*dst, base, out_dim * trailing);
1538 for v in out.iter_mut() {
1540 *v = 0.0;
1541 }
1542 for i in 0..num_updates {
1543 let row = ids[i] as usize;
1544 debug_assert!(row < out_dim, "ScatterAdd index out of range");
1545 let src_off = i * trailing;
1546 let dst_off = row * trailing;
1547 for j in 0..trailing {
1548 out[dst_off + j] += upd[src_off + j];
1549 }
1550 }
1551 }
1552 }
1553}
1554
1555#[inline(always)]
1556pub(crate) fn exec_scatter_nd(t: &Thunk, base: *mut u8) {
1557 let Thunk::ScatterNd {
1558 data,
1559 indices,
1560 updates,
1561 dst,
1562 data_shape,
1563 indices_shape,
1564 data_len,
1565 updates_len,
1566 indices_len,
1567 indices_i64,
1568 reduction,
1569 } = t
1570 else {
1571 unreachable!()
1572 };
1573 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
1574 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
1575 let data_len = *data_len as usize;
1576 let updates_len = *updates_len as usize;
1577 let indices_len = *indices_len as usize;
1578 unsafe {
1579 let data_s = sl(*data, base, data_len);
1580 let updates_s = sl(*updates, base, updates_len);
1581 let out = sl_mut(*dst, base, data_len);
1582 let idx_i64: Vec<i64> = if *indices_i64 != 0 {
1583 let ptr = base.add(*indices) as *const i64;
1584 std::slice::from_raw_parts(ptr, indices_len).to_vec()
1585 } else {
1586 sl(*indices, base, indices_len)
1587 .iter()
1588 .map(|&x| x as i64)
1589 .collect()
1590 };
1591 let (offsets, slice) =
1592 crate::onnx_indexing::scatter_nd_dst_offsets(&data_shape, &idx_i64, &indices_shape);
1593 crate::onnx_indexing::scatter_nd_into_f32(
1594 data_s, updates_s, out, &offsets, slice, *reduction,
1595 );
1596 }
1597}
1598
1599fn load_indices_i64(base: *mut u8, off: usize, len: usize, as_i64: u8) -> Vec<i64> {
1600 unsafe {
1601 if as_i64 != 0 {
1602 let ptr = base.add(off) as *const i64;
1603 std::slice::from_raw_parts(ptr, len).to_vec()
1604 } else {
1605 sl(off, base, len).iter().map(|&x| x as i64).collect()
1606 }
1607 }
1608}
1609
1610#[inline(always)]
1611pub(crate) fn exec_scatter_elements(t: &Thunk, base: *mut u8) {
1612 let Thunk::ScatterElements {
1613 data,
1614 indices,
1615 updates,
1616 dst,
1617 data_shape,
1618 data_len,
1619 updates_len,
1620 indices_len,
1621 indices_i64,
1622 axis,
1623 reduction,
1624 } = t
1625 else {
1626 unreachable!()
1627 };
1628 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
1629 unsafe {
1630 let data_s = sl(*data, base, *data_len as usize);
1631 let updates_s = sl(*updates, base, *updates_len as usize);
1632 let out = sl_mut(*dst, base, *data_len as usize);
1633 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
1634 crate::onnx_indexing::scatter_elements_f32(
1635 data_s,
1636 updates_s,
1637 &idx,
1638 out,
1639 &data_shape,
1640 *axis,
1641 *reduction,
1642 );
1643 }
1644}
1645
1646#[inline(always)]
1647pub(crate) fn exec_gather_nd(t: &Thunk, base: *mut u8) {
1648 let Thunk::GatherNd {
1649 data,
1650 indices,
1651 dst,
1652 data_shape,
1653 indices_shape,
1654 data_len,
1655 indices_len,
1656 out_len,
1657 indices_i64,
1658 batch_dims,
1659 } = t
1660 else {
1661 unreachable!()
1662 };
1663 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
1664 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
1665 unsafe {
1666 let data_s = sl(*data, base, *data_len as usize);
1667 let out = sl_mut(*dst, base, *out_len as usize);
1668 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
1669 let (offsets, slice) = crate::onnx_indexing::gather_nd_src_offsets(
1670 &data_shape,
1671 &idx,
1672 &indices_shape,
1673 (*batch_dims).max(0) as usize,
1674 );
1675 for (t, &off) in offsets.iter().enumerate() {
1676 let d0 = t * slice;
1677 for j in 0..slice {
1678 if let (Some(&s), Some(d)) = (data_s.get(off + j), out.get_mut(d0 + j)) {
1679 *d = s;
1680 }
1681 }
1682 }
1683 }
1684}
1685
1686#[inline(always)]
1687pub(crate) fn exec_gather_elements(t: &Thunk, base: *mut u8) {
1688 let Thunk::GatherElements {
1689 data,
1690 indices,
1691 dst,
1692 data_shape,
1693 indices_shape,
1694 data_len,
1695 indices_len,
1696 out_len,
1697 indices_i64,
1698 axis,
1699 } = t
1700 else {
1701 unreachable!()
1702 };
1703 let data_shape: Vec<usize> = data_shape.iter().map(|&d| d as usize).collect();
1704 let indices_shape: Vec<usize> = indices_shape.iter().map(|&d| d as usize).collect();
1705 unsafe {
1706 let data_s = sl(*data, base, *data_len as usize);
1707 let out = sl_mut(*dst, base, *out_len as usize);
1708 let idx = load_indices_i64(base, *indices, *indices_len as usize, *indices_i64);
1709 crate::onnx_indexing::gather_elements_f32(
1710 data_s,
1711 &idx,
1712 out,
1713 &data_shape,
1714 &indices_shape,
1715 *axis,
1716 );
1717 }
1718}
1719
1720#[inline(always)]
1721pub(crate) fn exec_relu_backward(t: &Thunk, base: *mut u8) {
1722 let Thunk::ReluBackward { x, dy, dx, len } = t else {
1723 unreachable!()
1724 };
1725 {
1726 let len = *len as usize;
1727 unsafe {
1728 let xs = sl(*x, base, len);
1729 let dys = sl(*dy, base, len);
1730 let out = sl_mut(*dx, base, len);
1731 if fast_conv_enabled() && crate::pool::should_parallelize(len) {
1732 let oa = out.as_mut_ptr() as usize;
1733 crate::pool::par_for(len, crate::pool::chunk_floor(len), &|off, cnt| {
1734 for i in off..off + cnt {
1735 *((oa as *mut f32).add(i)) = if xs[i] > 0.0 { dys[i] } else { 0.0 };
1736 }
1737 });
1738 } else {
1739 for i in 0..len {
1740 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
1741 }
1742 }
1743 }
1744 }
1745}
1746
1747#[inline(always)]
1748pub(crate) fn exec_relu_backward_f64(t: &Thunk, base: *mut u8) {
1749 let Thunk::ReluBackwardF64 { x, dy, dx, len } = t else {
1750 unreachable!()
1751 };
1752 {
1753 let len = *len as usize;
1754 unsafe {
1755 let xs = sl_f64(*x, base, len);
1756 let dys = sl_f64(*dy, base, len);
1757 let out = sl_mut_f64(*dx, base, len);
1758 for i in 0..len {
1759 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
1760 }
1761 }
1762 }
1763}
1764
1765#[inline(always)]
1766pub(crate) fn exec_activation_backward(t: &Thunk, base: *mut u8) {
1767 let Thunk::ActivationBackward {
1768 x,
1769 dy,
1770 dx,
1771 len,
1772 kind,
1773 } = t
1774 else {
1775 unreachable!()
1776 };
1777 {
1778 let len = *len as usize;
1779 unsafe {
1780 let xs = sl(*x, base, len);
1781 let dys = sl(*dy, base, len);
1782 let out = sl_mut(*dx, base, len);
1783 activation_backward_kernel(*kind, xs, dys, out);
1784 }
1785 }
1786}
1787
1788#[inline(always)]
1789pub(crate) fn exec_activation_backward_f64(t: &Thunk, base: *mut u8) {
1790 let Thunk::ActivationBackwardF64 {
1791 x,
1792 dy,
1793 dx,
1794 len,
1795 kind,
1796 } = t
1797 else {
1798 unreachable!()
1799 };
1800 {
1801 let len = *len as usize;
1802 unsafe {
1803 let xs = sl_f64(*x, base, len);
1804 let dys = sl_f64(*dy, base, len);
1805 let out = sl_mut_f64(*dx, base, len);
1806 activation_backward_kernel_f64(*kind, xs, dys, out);
1807 }
1808 }
1809}
1810
1811#[inline(always)]
1812pub(crate) fn exec_gather_backward(t: &Thunk, base: *mut u8) {
1813 let Thunk::GatherBackward {
1814 dy,
1815 indices,
1816 dst,
1817 outer,
1818 axis_dim,
1819 num_idx,
1820 trailing,
1821 } = t
1822 else {
1823 unreachable!()
1824 };
1825 {
1826 let (outer, axis_dim, num_idx, trailing) = (
1827 *outer as usize,
1828 *axis_dim as usize,
1829 *num_idx as usize,
1830 *trailing as usize,
1831 );
1832 unsafe {
1833 let dys = sl(*dy, base, outer * num_idx * trailing);
1834 let ids = sl(*indices, base, num_idx);
1835 let out = sl_mut(*dst, base, outer * axis_dim * trailing);
1836 for v in out.iter_mut() {
1837 *v = 0.0;
1838 }
1839 crate::training_bwd::gather_axis_backward(
1840 dys, ids, out, outer, axis_dim, num_idx, trailing,
1841 );
1842 }
1843 }
1844}
1845
1846#[inline(always)]
1847pub(crate) fn exec_gather_axis(t: &Thunk, base: *mut u8) {
1848 let Thunk::GatherAxis {
1849 table,
1850 idx,
1851 dst,
1852 outer,
1853 axis_dim,
1854 num_idx,
1855 trailing,
1856 idx_i64,
1857 table_bytes,
1858 } = t
1859 else {
1860 unreachable!()
1861 };
1862 {
1863 let outer = *outer as usize;
1864 let axis_dim = *axis_dim as usize;
1865 let num_idx = *num_idx as usize;
1866 let trailing = *trailing as usize;
1867 unsafe {
1868 if *table_bytes == 8 {
1869 let tab = sl_i64(*table, base, outer * axis_dim * trailing);
1870 let out = sl_mut_i64(*dst, base, outer * num_idx * trailing);
1871 for o in 0..outer {
1872 let tab_outer = o * axis_dim * trailing;
1873 let out_outer = o * num_idx * trailing;
1874 if *idx_i64 != 0 {
1875 let ids = sl_i64(*idx, base, num_idx);
1876 for k in 0..num_idx {
1877 let row = ids[k].max(0) as usize;
1878 if row < axis_dim {
1879 let tab_row = tab_outer + row * trailing;
1880 let out_row = out_outer + k * trailing;
1881 out[out_row..out_row + trailing]
1882 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
1883 }
1884 }
1885 } else {
1886 let ids = sl(*idx, base, num_idx);
1887 for k in 0..num_idx {
1888 let row = ids[k] as usize;
1889 if row < axis_dim {
1890 let tab_row = tab_outer + row * trailing;
1891 let out_row = out_outer + k * trailing;
1892 out[out_row..out_row + trailing]
1893 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
1894 }
1895 }
1896 }
1897 }
1898 } else {
1899 let tab = sl(*table, base, outer * axis_dim * trailing);
1900 let out = sl_mut(*dst, base, outer * num_idx * trailing);
1901 for o in 0..outer {
1902 let tab_outer = o * axis_dim * trailing;
1903 let out_outer = o * num_idx * trailing;
1904 if *idx_i64 != 0 {
1905 let ids = sl_i64(*idx, base, num_idx);
1906 for k in 0..num_idx {
1907 let row = ids[k].max(0) as usize;
1908 if row < axis_dim {
1909 let tab_row = tab_outer + row * trailing;
1910 let out_row = out_outer + k * trailing;
1911 out[out_row..out_row + trailing]
1912 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
1913 }
1914 }
1915 } else {
1916 let ids = sl(*idx, base, num_idx);
1917 for k in 0..num_idx {
1918 let row = ids[k] as usize;
1919 if row < axis_dim {
1920 let tab_row = tab_outer + row * trailing;
1921 let out_row = out_outer + k * trailing;
1922 out[out_row..out_row + trailing]
1923 .copy_from_slice(&tab[tab_row..tab_row + trailing]);
1924 }
1925 }
1926 }
1927 }
1928 }
1929 }
1930 }
1931}
1932
1933#[inline(always)]
1934pub(crate) fn exec_reverse(t: &Thunk, base: *mut u8) {
1935 let Thunk::Reverse {
1936 src,
1937 dst,
1938 dims,
1939 rev_mask,
1940 elem_bytes,
1941 } = t
1942 else {
1943 unreachable!()
1944 };
1945 {
1946 let eb = *elem_bytes as usize;
1947 let rank = dims.len();
1948 let total: usize = dims.iter().map(|&d| d as usize).product::<usize>().max(1);
1949 let mut strides = vec![1usize; rank];
1950 for i in (0..rank.saturating_sub(1)).rev() {
1951 strides[i] = strides[i + 1] * dims[i + 1] as usize;
1952 }
1953 unsafe {
1954 let src_base = base.add(*src);
1955 let dst_base = base.add(*dst);
1956 for o in 0..total {
1957 let mut rem = o;
1958 let mut in_flat = 0usize;
1959 for ax in 0..rank {
1960 let idx = rem / strides[ax];
1961 rem %= strides[ax];
1962 let in_idx = if rev_mask[ax] {
1963 dims[ax] as usize - 1 - idx
1964 } else {
1965 idx
1966 };
1967 in_flat += in_idx * strides[ax];
1968 }
1969 std::ptr::copy_nonoverlapping(src_base.add(in_flat * eb), dst_base.add(o * eb), eb);
1970 }
1971 }
1972 }
1973}
1974
1975pub unsafe fn execute_reverse(
1981 src: usize,
1982 dst: usize,
1983 dims: &[u32],
1984 rev_mask: &[bool],
1985 elem_bytes: usize,
1986 base: *mut u8,
1987) {
1988 let rank = dims.len();
1989 let total: usize = dims.iter().map(|&d| d as usize).product::<usize>().max(1);
1990 let mut strides = vec![1usize; rank];
1991 for i in (0..rank.saturating_sub(1)).rev() {
1992 strides[i] = strides[i + 1] * dims[i + 1] as usize;
1993 }
1994 unsafe {
1995 let src_base = base.add(src);
1996 let dst_base = base.add(dst);
1997 for o in 0..total {
1998 let mut rem = o;
1999 let mut in_flat = 0usize;
2000 for ax in 0..rank {
2001 let idx = rem / strides[ax];
2002 rem %= strides[ax];
2003 let in_idx = if rev_mask[ax] {
2004 dims[ax] as usize - 1 - idx
2005 } else {
2006 idx
2007 };
2008 in_flat += in_idx * strides[ax];
2009 }
2010 std::ptr::copy_nonoverlapping(
2011 src_base.add(in_flat * elem_bytes),
2012 dst_base.add(o * elem_bytes),
2013 elem_bytes,
2014 );
2015 }
2016 }
2017}
2018
2019pub unsafe fn execute_gather_backward_f32(
2020 dy: usize,
2021 indices: usize,
2022 dst: usize,
2023 outer: u32,
2024 axis_dim: u32,
2025 num_idx: u32,
2026 trailing: u32,
2027 base: *mut u8,
2028) {
2029 let (outer, axis_dim, num_idx, trailing) = (
2030 outer as usize,
2031 axis_dim as usize,
2032 num_idx as usize,
2033 trailing as usize,
2034 );
2035 let out = sl_mut(dst, base, outer * axis_dim * trailing);
2036 out.fill(0.0);
2037 crate::training_bwd::gather_axis_backward(
2038 sl(dy, base, outer * num_idx * trailing),
2039 sl(indices, base, num_idx),
2040 out,
2041 outer,
2042 axis_dim,
2043 num_idx,
2044 trailing,
2045 );
2046}
2047
2048#[inline]
2051pub(crate) fn region_activation_scalar(act: rlx_ir::op::Activation, x: f32) -> f32 {
2052 use rlx_ir::op::Activation as A;
2053 const GC: f32 = 0.797_884_6; match act {
2055 A::Relu => x.max(0.0),
2056 A::Gelu | A::GeluApprox => 0.5 * x * (1.0 + (GC * (x + 0.044715 * x * x * x)).tanh()),
2057 A::Silu => x / (1.0 + (-x).exp()),
2058 A::Sigmoid => 1.0 / (1.0 + (-x).exp()),
2059 A::Tanh => x.tanh(),
2060 A::Exp => x.exp(),
2061 A::Log => x.ln(),
2062 A::Sqrt => x.sqrt(),
2063 A::Rsqrt => 1.0 / x.sqrt(),
2064 A::Neg => -x,
2065 A::Abs => x.abs(),
2066 A::Sin => x.sin(),
2067 A::Cos => x.cos(),
2068 A::Tan => x.tan(),
2069 A::Atan => x.atan(),
2070 A::Round => x.round(),
2071 }
2072}
2073
2074#[inline]
2075pub(crate) fn region_binary_scalar(op: rlx_ir::op::BinaryOp, l: f32, r: f32) -> f32 {
2076 use rlx_ir::op::BinaryOp as B;
2077 match op {
2078 B::Add => l + r,
2079 B::Sub => l - r,
2080 B::Mul => l * r,
2081 B::Div => l / r,
2082 B::Max => l.max(r),
2083 B::Min => l.min(r),
2084 B::Pow => l.powf(r),
2085 }
2086}
2087
2088#[inline]
2089pub(crate) fn region_compare_scalar(op: rlx_ir::op::CmpOp, l: f32, r: f32) -> bool {
2090 use rlx_ir::op::CmpOp as C;
2091 match op {
2092 C::Eq => l == r,
2093 C::Ne => l != r,
2094 C::Lt => l < r,
2095 C::Le => l <= r,
2096 C::Gt => l > r,
2097 C::Ge => l >= r,
2098 }
2099}
2100
2101#[inline]
2105pub(crate) fn region_resolve_operand(
2106 op: &rlx_ir::op::ChainOperand,
2107 gid: usize,
2108 base: *const u8,
2109 input_offs: &[usize],
2110 scalar_mask: u32,
2111 modulus: &[u32; 16],
2112 scratch: &[f32; 32],
2113) -> f32 {
2114 use rlx_ir::op::ChainOperand as O;
2115 match op {
2116 O::Step(s) => scratch[*s as usize],
2117 O::Input(i) => {
2118 let i = *i as usize;
2119 let row = if (scalar_mask >> i) & 1 == 1 {
2120 0
2121 } else if modulus[i] != 0 {
2122 gid % modulus[i] as usize
2123 } else {
2124 gid
2125 };
2126 unsafe { *(base.add(input_offs[i]) as *const f32).add(row) }
2127 }
2128 }
2129}
2130
2131#[inline]
2134pub(crate) fn region_eval_elem(
2135 gid: usize,
2136 base: *const u8,
2137 input_offs: &[usize],
2138 chain: &[rlx_ir::op::ChainStep],
2139 scalar_mask: u32,
2140 modulus: &[u32; 16],
2141) -> f32 {
2142 use rlx_ir::op::ChainStep as S;
2143 let mut scratch = [0f32; 32];
2144 let r = |o: &rlx_ir::op::ChainOperand, sc: &[f32; 32]| {
2145 region_resolve_operand(o, gid, base, input_offs, scalar_mask, modulus, sc)
2146 };
2147 for (k, step) in chain.iter().enumerate() {
2148 scratch[k] = match step {
2149 S::Activation(a, x) => region_activation_scalar(*a, r(x, &scratch)),
2150 S::Cast(_, x) => r(x, &scratch), S::Binary(op, l, rr) => region_binary_scalar(*op, r(l, &scratch), r(rr, &scratch)),
2152 S::Compare(op, l, rr) => {
2153 if region_compare_scalar(*op, r(l, &scratch), r(rr, &scratch)) {
2154 1.0
2155 } else {
2156 0.0
2157 }
2158 }
2159 S::Where(c, t, f) => {
2160 if r(c, &scratch) != 0.0 {
2161 r(t, &scratch)
2162 } else {
2163 r(f, &scratch)
2164 }
2165 }
2166 };
2167 }
2168 scratch[chain.len() - 1]
2169}
2170
2171#[inline(always)]
2176pub(crate) fn apply_activation_inplace(d: &mut [f32], act: rlx_ir::op::Activation) {
2177 use rlx_ir::op::Activation;
2178 match act {
2179 Activation::Gelu => crate::kernels::par_gelu_inplace(d),
2180 Activation::GeluApprox => crate::kernels::par_gelu_approx_inplace(d),
2181 Activation::Silu => crate::kernels::par_silu_inplace(d),
2182 Activation::Relu => {
2183 for v in d.iter_mut() {
2184 *v = v.max(0.0);
2185 }
2186 }
2187 Activation::Sigmoid => {
2188 for v in d.iter_mut() {
2189 *v = 1.0 / (1.0 + (-*v).exp());
2190 }
2191 }
2192 Activation::Tanh => {
2193 for v in d.iter_mut() {
2194 *v = v.tanh();
2195 }
2196 }
2197 Activation::Exp => {
2198 for v in d.iter_mut() {
2199 *v = v.exp();
2200 }
2201 }
2202 Activation::Log => {
2203 for v in d.iter_mut() {
2204 *v = v.ln();
2205 }
2206 }
2207 Activation::Sqrt => {
2208 for v in d.iter_mut() {
2209 *v = v.sqrt();
2210 }
2211 }
2212 Activation::Rsqrt => {
2213 for v in d.iter_mut() {
2214 *v = 1.0 / v.sqrt();
2215 }
2216 }
2217 Activation::Neg => {
2218 for v in d.iter_mut() {
2219 *v = -*v;
2220 }
2221 }
2222 Activation::Abs => {
2223 for v in d.iter_mut() {
2224 *v = v.abs();
2225 }
2226 }
2227 Activation::Round => {
2228 for v in d.iter_mut() {
2229 *v = v.round();
2230 }
2231 }
2232 Activation::Sin => {
2233 for v in d.iter_mut() {
2234 *v = v.sin();
2235 }
2236 }
2237 Activation::Cos => {
2238 for v in d.iter_mut() {
2239 *v = v.cos();
2240 }
2241 }
2242 Activation::Tan => {
2243 for v in d.iter_mut() {
2244 *v = v.tan();
2245 }
2246 }
2247 Activation::Atan => {
2248 for v in d.iter_mut() {
2249 *v = v.atan();
2250 }
2251 }
2252 }
2253}
2254
2255pub(crate) fn activation_backward_kernel(
2256 act: rlx_ir::op::Activation,
2257 xs: &[f32],
2258 dys: &[f32],
2259 out: &mut [f32],
2260) {
2261 use rlx_ir::op::Activation;
2262 let n = xs.len();
2263 debug_assert_eq!(dys.len(), n);
2264 debug_assert_eq!(out.len(), n);
2265 match act {
2266 Activation::Relu => {
2267 for i in 0..n {
2268 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2269 }
2270 }
2271 Activation::Sigmoid => {
2272 for i in 0..n {
2273 let s = 1.0 / (1.0 + (-xs[i]).exp());
2274 out[i] = s * (1.0 - s) * dys[i];
2275 }
2276 }
2277 Activation::Tanh => {
2278 for i in 0..n {
2279 let t = xs[i].tanh();
2280 out[i] = (1.0 - t * t) * dys[i];
2281 }
2282 }
2283 Activation::Silu => {
2284 for i in 0..n {
2286 let s = 1.0 / (1.0 + (-xs[i]).exp());
2287 out[i] = s * (1.0 + xs[i] * (1.0 - s)) * dys[i];
2288 }
2289 }
2290 Activation::Gelu => {
2291 const INV_SQRT2: f32 = 0.707_106_77;
2294 const INV_SQRT_2PI: f32 = 0.398_942_3;
2295 for i in 0..n {
2296 let x = xs[i];
2297 let phi = 0.5 * (1.0 + erf_f32(x * INV_SQRT2));
2298 let pdf = INV_SQRT_2PI * (-(x * x) * 0.5).exp();
2299 out[i] = (phi + x * pdf) * dys[i];
2300 }
2301 }
2302 Activation::GeluApprox => {
2303 const C: f32 = 0.797_884_6; const A: f32 = 0.044_715;
2307 for i in 0..n {
2308 let x = xs[i];
2309 let inner = C * (x + A * x * x * x);
2310 let t = inner.tanh();
2311 let dinner = C * (1.0 + 3.0 * A * x * x);
2312 let d = 0.5 * (1.0 + t) + 0.5 * x * (1.0 - t * t) * dinner;
2313 out[i] = d * dys[i];
2314 }
2315 }
2316 Activation::Exp => {
2317 for i in 0..n {
2318 out[i] = xs[i].exp() * dys[i];
2319 }
2320 }
2321 Activation::Log => {
2322 for i in 0..n {
2323 out[i] = dys[i] / xs[i];
2324 }
2325 }
2326 Activation::Sqrt => {
2327 for i in 0..n {
2329 let s = xs[i].sqrt();
2330 out[i] = if s > 0.0 { 0.5 * dys[i] / s } else { 0.0 };
2331 }
2332 }
2333 Activation::Rsqrt => {
2334 for i in 0..n {
2336 let s = xs[i].sqrt();
2337 out[i] = if s > 0.0 {
2338 -0.5 * dys[i] / (xs[i] * s)
2339 } else {
2340 0.0
2341 };
2342 }
2343 }
2344 Activation::Neg => {
2345 for i in 0..n {
2346 out[i] = -dys[i];
2347 }
2348 }
2349 Activation::Abs => {
2350 for i in 0..n {
2352 let x = xs[i];
2353 let s = if x > 0.0 {
2354 1.0
2355 } else if x < 0.0 {
2356 -1.0
2357 } else {
2358 0.0
2359 };
2360 out[i] = s * dys[i];
2361 }
2362 }
2363 Activation::Round => {
2364 out.copy_from_slice(dys);
2369 }
2370 Activation::Sin => {
2371 for i in 0..n {
2373 out[i] = xs[i].cos() * dys[i];
2374 }
2375 }
2376 Activation::Cos => {
2377 for i in 0..n {
2378 out[i] = -xs[i].sin() * dys[i];
2379 }
2380 }
2381 Activation::Tan => {
2382 for i in 0..n {
2384 let t = xs[i].tan();
2385 out[i] = (1.0 + t * t) * dys[i];
2386 }
2387 }
2388 Activation::Atan => {
2389 for i in 0..n {
2391 let x = xs[i];
2392 out[i] = dys[i] / (1.0 + x * x);
2393 }
2394 }
2395 }
2396}
2397
2398pub(crate) fn activation_backward_kernel_f64(
2402 act: rlx_ir::op::Activation,
2403 xs: &[f64],
2404 dys: &[f64],
2405 out: &mut [f64],
2406) {
2407 use rlx_ir::op::Activation;
2408 let n = xs.len();
2409 debug_assert_eq!(dys.len(), n);
2410 debug_assert_eq!(out.len(), n);
2411 match act {
2412 Activation::Relu => {
2413 for i in 0..n {
2414 out[i] = if xs[i] > 0.0 { dys[i] } else { 0.0 };
2415 }
2416 }
2417 Activation::Sigmoid => {
2418 for i in 0..n {
2419 let s = 1.0 / (1.0 + (-xs[i]).exp());
2420 out[i] = s * (1.0 - s) * dys[i];
2421 }
2422 }
2423 Activation::Tanh => {
2424 for i in 0..n {
2425 let t = xs[i].tanh();
2426 out[i] = (1.0 - t * t) * dys[i];
2427 }
2428 }
2429 Activation::Silu => {
2430 for i in 0..n {
2431 let s = 1.0 / (1.0 + (-xs[i]).exp());
2432 out[i] = s * (1.0 + xs[i] * (1.0 - s)) * dys[i];
2433 }
2434 }
2435 Activation::Gelu | Activation::GeluApprox => {
2436 const INV_SQRT2: f64 = std::f64::consts::FRAC_1_SQRT_2;
2438 const INV_SQRT_2PI: f64 = 0.398_942_280_401_432_7;
2439 for i in 0..n {
2440 let x = xs[i];
2441 let phi = 0.5 * (1.0 + erf_f64(x * INV_SQRT2));
2442 let pdf = INV_SQRT_2PI * (-(x * x) * 0.5).exp();
2443 out[i] = (phi + x * pdf) * dys[i];
2444 }
2445 }
2446 Activation::Exp => {
2447 for i in 0..n {
2448 out[i] = xs[i].exp() * dys[i];
2449 }
2450 }
2451 Activation::Log => {
2452 for i in 0..n {
2453 out[i] = dys[i] / xs[i];
2454 }
2455 }
2456 Activation::Sqrt => {
2457 for i in 0..n {
2458 let s = xs[i].sqrt();
2459 out[i] = if s > 0.0 { 0.5 * dys[i] / s } else { 0.0 };
2460 }
2461 }
2462 Activation::Rsqrt => {
2463 for i in 0..n {
2464 let s = xs[i].sqrt();
2465 out[i] = if s > 0.0 {
2466 -0.5 * dys[i] / (xs[i] * s)
2467 } else {
2468 0.0
2469 };
2470 }
2471 }
2472 Activation::Neg => {
2473 for i in 0..n {
2474 out[i] = -dys[i];
2475 }
2476 }
2477 Activation::Abs => {
2478 for i in 0..n {
2479 let x = xs[i];
2480 let s = if x > 0.0 {
2481 1.0
2482 } else if x < 0.0 {
2483 -1.0
2484 } else {
2485 0.0
2486 };
2487 out[i] = s * dys[i];
2488 }
2489 }
2490 Activation::Round => {
2491 out.copy_from_slice(dys);
2492 }
2493 Activation::Sin => {
2494 for i in 0..n {
2495 out[i] = xs[i].cos() * dys[i];
2496 }
2497 }
2498 Activation::Cos => {
2499 for i in 0..n {
2500 out[i] = -xs[i].sin() * dys[i];
2501 }
2502 }
2503 Activation::Tan => {
2504 for i in 0..n {
2505 let t = xs[i].tan();
2506 out[i] = (1.0 + t * t) * dys[i];
2507 }
2508 }
2509 Activation::Atan => {
2510 for i in 0..n {
2511 let x = xs[i];
2512 out[i] = dys[i] / (1.0 + x * x);
2513 }
2514 }
2515 }
2516}
2517
2518#[inline(always)]
2523pub(crate) fn erf_f64(x: f64) -> f64 {
2524 let s = x.signum();
2525 let x = x.abs();
2526 let t = 1.0 / (1.0 + 0.327_591_1 * x);
2527 let y = 1.0
2528 - (((((1.061_405_43 * t - 1.453_152_03) * t) + 1.421_413_75) * t - 0.284_496_74) * t
2529 + 0.254_829_59)
2530 * t
2531 * (-x * x).exp();
2532 s * y
2533}
2534
2535#[inline(always)]
2538pub(crate) fn erf_f32(x: f32) -> f32 {
2539 let s = x.signum();
2540 let x = x.abs();
2541 let t = 1.0 / (1.0 + 0.327_591_1 * x);
2542 let y = 1.0
2543 - (((((1.061_405_4 * t - 1.453_152_1) * t) + 1.421_413_8) * t - 0.284_496_74) * t
2544 + 0.254_829_6)
2545 * t
2546 * (-x * x).exp();
2547 s * y
2548}
2549
2550pub(crate) fn narrow_thunk_closure(
2551 src: usize,
2552 dst: usize,
2553 outer: u32,
2554 src_stride: u32,
2555 dst_stride: u32,
2556 inner: u32,
2557 elem_bytes: u8,
2558) -> Arc<dyn Fn(*mut u8) + Send + Sync> {
2559 let (outer, ss, ds, inner, eb) = (
2560 outer as usize,
2561 src_stride as usize,
2562 dst_stride as usize,
2563 inner as usize,
2564 elem_bytes as usize,
2565 );
2566 let row_bytes = inner.saturating_mul(eb);
2567 let src_row_stride = ss.saturating_mul(eb);
2568 let dst_row_stride = ds.saturating_mul(eb);
2569 Arc::new(move |base: *mut u8| unsafe {
2570 if row_bytes == 0 || src == dst {
2571 return;
2572 }
2573 let arena_len = usize::MAX;
2575 for o in 0..outer {
2576 let s_off = src + o * src_row_stride;
2577 let d_off = dst + o * dst_row_stride;
2578 if s_off == d_off {
2579 continue;
2580 }
2581 if s_off.saturating_add(row_bytes) > arena_len
2582 || d_off.saturating_add(row_bytes) > arena_len
2583 {
2584 break;
2585 }
2586 std::ptr::copy_nonoverlapping(base.add(s_off), base.add(d_off), row_bytes);
2587 }
2588 })
2589}
2590
2591pub(crate) fn transpose_walk_f64(
2595 inp: &[f64],
2596 out: &mut [f64],
2597 out_dims: &[u32],
2598 in_strides: &[u32],
2599) {
2600 let rank = out_dims.len();
2601 let mut idx = vec![0u32; rank];
2602 for o in 0..out.len() {
2603 let mut src_off = 0usize;
2604 for d in 0..rank {
2605 src_off += idx[d] as usize * in_strides[d] as usize;
2606 }
2607 out[o] = inp[broadcast_src_index(src_off, inp.len())];
2608 for d in (0..rank).rev() {
2610 idx[d] += 1;
2611 if idx[d] < out_dims[d] {
2612 break;
2613 }
2614 idx[d] = 0;
2615 }
2616 }
2617}
2618
2619pub(crate) fn apply_activation_f64(inp: &[f64], out: &mut [f64], kind: Activation) {
2625 match kind {
2626 Activation::Neg => {
2627 for (o, &v) in out.iter_mut().zip(inp) {
2628 *o = -v;
2629 }
2630 }
2631 Activation::Exp => {
2632 for (o, &v) in out.iter_mut().zip(inp) {
2633 *o = v.exp();
2634 }
2635 }
2636 Activation::Log => {
2637 for (o, &v) in out.iter_mut().zip(inp) {
2638 *o = v.ln();
2639 }
2640 }
2641 Activation::Sqrt => {
2642 for (o, &v) in out.iter_mut().zip(inp) {
2643 *o = v.sqrt();
2644 }
2645 }
2646 Activation::Rsqrt => {
2647 for (o, &v) in out.iter_mut().zip(inp) {
2648 *o = 1.0 / v.sqrt();
2649 }
2650 }
2651 Activation::Abs => {
2652 for (o, &v) in out.iter_mut().zip(inp) {
2653 *o = v.abs();
2654 }
2655 }
2656 Activation::Tanh => {
2657 for (o, &v) in out.iter_mut().zip(inp) {
2658 *o = v.tanh();
2659 }
2660 }
2661 Activation::Sigmoid => {
2662 for (o, &v) in out.iter_mut().zip(inp) {
2663 *o = 1.0 / (1.0 + (-v).exp());
2664 }
2665 }
2666 Activation::Relu => {
2667 for (o, &v) in out.iter_mut().zip(inp) {
2668 *o = v.max(0.0);
2669 }
2670 }
2671 Activation::Round => {
2672 for (o, &v) in out.iter_mut().zip(inp) {
2673 *o = v.round_ties_even();
2674 }
2675 }
2676 Activation::Sin => {
2677 for (o, &v) in out.iter_mut().zip(inp) {
2678 *o = v.sin();
2679 }
2680 }
2681 Activation::Cos => {
2682 for (o, &v) in out.iter_mut().zip(inp) {
2683 *o = v.cos();
2684 }
2685 }
2686 Activation::Tan => {
2687 for (o, &v) in out.iter_mut().zip(inp) {
2688 *o = v.tan();
2689 }
2690 }
2691 Activation::Atan => {
2692 for (o, &v) in out.iter_mut().zip(inp) {
2693 *o = v.atan();
2694 }
2695 }
2696 Activation::Gelu | Activation::GeluApprox | Activation::Silu => {
2697 panic!(
2698 "apply_activation_f64: {kind:?} not yet implemented at f64. \
2699 Add when a workload needs it."
2700 );
2701 }
2702 }
2703}
2704
2705#[inline]
2706pub(crate) fn binary_op_f64(op: BinaryOp, a: f64, b: f64) -> f64 {
2707 match op {
2708 BinaryOp::Add => a + b,
2709 BinaryOp::Sub => a - b,
2710 BinaryOp::Mul => a * b,
2711 BinaryOp::Div => a / b,
2712 BinaryOp::Max => a.max(b),
2713 BinaryOp::Min => a.min(b),
2714 BinaryOp::Pow => a.powf(b),
2715 }
2716}