1#![allow(unsafe_op_in_unsafe_fn)]
2use crate::thunk::*;
3
4pub(crate) fn sample_row(
13 logits: &[f32],
14 top_k: usize,
15 top_p: f32,
16 temperature: f32,
17 rng: &mut rlx_ir::Philox4x32,
18) -> usize {
19 let v = logits.len();
20 if v == 0 {
21 return 0;
22 }
23 let temp = temperature.max(1e-6);
24 let mut scaled: Vec<f32> = logits.iter().map(|&x| x / temp).collect();
26
27 if top_k > 0 && top_k < v {
29 let mut indexed: Vec<(usize, f32)> = scaled.iter().copied().enumerate().collect();
31 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
34 let cutoff = indexed[top_k - 1].1;
35 for x in scaled.iter_mut() {
36 if *x < cutoff {
37 *x = f32::NEG_INFINITY;
38 }
39 }
40 }
41
42 let mut max_l = f32::NEG_INFINITY;
44 for &x in &scaled {
45 if x > max_l {
46 max_l = x;
47 }
48 }
49 let mut sum = 0.0f32;
50 for x in scaled.iter_mut() {
51 *x = (*x - max_l).exp();
52 sum += *x;
53 }
54 let inv = 1.0 / sum.max(f32::MIN_POSITIVE);
55 for x in scaled.iter_mut() {
56 *x *= inv;
57 }
58
59 if top_p < 1.0 {
62 let mut indexed: Vec<(usize, f32)> = scaled.iter().copied().enumerate().collect();
63 indexed.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
64 let mut cum = 0.0f32;
65 let mut keep = vec![false; v];
66 for (idx, p) in indexed.iter() {
67 keep[*idx] = true;
68 cum += *p;
69 if cum >= top_p {
70 break;
71 }
72 }
73 let mut new_sum = 0.0f32;
74 for (i, x) in scaled.iter_mut().enumerate() {
75 if !keep[i] {
76 *x = 0.0;
77 }
78 new_sum += *x;
79 }
80 let inv = 1.0 / new_sum.max(f32::MIN_POSITIVE);
81 for x in scaled.iter_mut() {
82 *x *= inv;
83 }
84 }
85
86 let r = rng.next_f32();
88 let mut acc = 0.0f32;
89 for (i, &p) in scaled.iter().enumerate() {
90 acc += p;
91 if r <= acc {
92 return i;
93 }
94 }
95 v - 1 }
97
98#[allow(unused_variables)]
99pub(crate) fn compile_sample(
100 node: &rlx_ir::Node,
101 graph: &Graph,
102 arena: &crate::arena::Arena,
103 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
104 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
105 rng: rlx_ir::RngOptions,
106) -> Thunk {
107 let Op::Sample {
108 top_k,
109 top_p,
110 temperature,
111 seed,
112 } = &node.op
113 else {
114 unreachable!()
115 };
116 {
117 let in_shape = &graph.node(node.inputs[0]).shape;
118 let (batch, vocab) = if in_shape.rank() >= 2 {
120 (
121 in_shape.dim(0).unwrap_static(),
122 in_shape.dim(in_shape.rank() - 1).unwrap_static(),
123 )
124 } else {
125 (1, in_shape.num_elements().unwrap_or(0))
126 };
127 Thunk::Sample {
128 logits: node_offset(arena, node.inputs[0]),
129 dst: node_offset(arena, node.id),
130 batch: batch as u32,
131 vocab: vocab as u32,
132 top_k: *top_k as u32,
133 top_p: *top_p,
134 temperature: *temperature,
135 seed: *seed,
136 }
137 }
138}
139
140#[allow(unused_variables)]
141pub(crate) fn compile_rng_normal(
142 node: &rlx_ir::Node,
143 graph: &Graph,
144 arena: &crate::arena::Arena,
145 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
146 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
147 rng: rlx_ir::RngOptions,
148) -> Thunk {
149 let Op::RngNormal {
150 mean,
151 scale,
152 key,
153 op_seed,
154 } = &node.op
155 else {
156 unreachable!()
157 };
158 Thunk::RngNormal {
159 dst: node_offset(arena, node.id),
160 len: node.shape.num_elements().unwrap_or(0) as u32,
161 mean: *mean,
162 scale: *scale,
163 key: *key,
164 op_seed: *op_seed,
165 }
166}
167
168#[allow(unused_variables)]
169pub(crate) fn compile_rng_uniform(
170 node: &rlx_ir::Node,
171 graph: &Graph,
172 arena: &crate::arena::Arena,
173 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
174 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
175 rng: rlx_ir::RngOptions,
176) -> Thunk {
177 let Op::RngUniform {
178 low,
179 high,
180 key,
181 op_seed,
182 } = &node.op
183 else {
184 unreachable!()
185 };
186 Thunk::RngUniform {
187 dst: node_offset(arena, node.id),
188 len: node.shape.num_elements().unwrap_or(0) as u32,
189 low: *low,
190 high: *high,
191 key: *key,
192 op_seed: *op_seed,
193 }
194}
195
196#[allow(unused_variables)]
197pub(crate) fn compile_cumsum(
198 node: &rlx_ir::Node,
199 graph: &Graph,
200 arena: &crate::arena::Arena,
201 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
202 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
203 rng: rlx_ir::RngOptions,
204) -> Thunk {
205 let Op::Cumsum { axis, exclusive } = &node.op else {
206 unreachable!()
207 };
208 {
209 let rank = node.shape.rank();
214 let ax = if *axis < 0 {
215 (rank as i32 + axis) as usize
216 } else {
217 *axis as usize
218 };
219 assert_eq!(
220 ax,
221 rank - 1,
222 "Cumsum only supports the last axis on CPU today"
223 );
224 let cols = node.shape.dim(ax).unwrap_static();
225 let total = node.shape.num_elements().unwrap();
226 Thunk::Cumsum {
227 src: node_offset(arena, node.inputs[0]),
228 dst: node_offset(arena, node.id),
229 rows: (total / cols) as u32,
230 cols: cols as u32,
231 exclusive: *exclusive,
232 dtype: node.shape.dtype(),
233 }
234 }
235}
236
237#[allow(unused_variables)]
238pub(crate) fn compile_top_k(
239 node: &rlx_ir::Node,
240 graph: &Graph,
241 arena: &crate::arena::Arena,
242 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
243 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
244 rng: rlx_ir::RngOptions,
245) -> Thunk {
246 let Op::TopK { k } = &node.op else {
247 unreachable!()
248 };
249 {
250 let in_shape = &graph.node(node.inputs[0]).shape;
251 let rank = in_shape.rank();
252 let axis_dim = in_shape.dim(rank - 1).unwrap_static();
253 let outer = in_shape.num_elements().unwrap() / axis_dim;
254 let indices_i64 = u8::from(graph.node(node.id).shape.dtype() == rlx_ir::DType::I64);
255 Thunk::TopK {
256 src: node_offset(arena, node.inputs[0]),
257 dst: node_offset(arena, node.id),
258 outer: outer as u32,
259 axis_dim: axis_dim as u32,
260 k: *k as u32,
261 indices_i64,
262 }
263 }
264}
265
266#[allow(unused_variables)]
267pub(crate) fn compile_reduce(
268 node: &rlx_ir::Node,
269 graph: &Graph,
270 arena: &crate::arena::Arena,
271 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
272 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
273 rng: rlx_ir::RngOptions,
274) -> Thunk {
275 let Op::Reduce {
276 op,
277 axes,
278 keep_dim: _,
279 } = &node.op
280 else {
281 unreachable!()
282 };
283 {
284 let in_shape = &graph.node(node.inputs[0]).shape;
290 let rank = in_shape.rank();
291 let mut sorted = axes.clone();
292 sorted.sort();
293 sorted.dedup();
294 let contiguous = sorted.windows(2).all(|w| w[1] == w[0] + 1)
295 && !sorted.is_empty()
296 && *sorted.last().unwrap() < rank;
297 if !contiguous {
298 Thunk::Nop
299 } else {
300 let first = sorted[0];
301 let last = *sorted.last().unwrap();
302 let outer: usize = (0..first)
303 .map(|i| in_shape.dim(i).unwrap_static())
304 .product::<usize>()
305 .max(1);
306 let reduced: usize = (first..=last)
307 .map(|i| in_shape.dim(i).unwrap_static())
308 .product();
309 let inner: usize = (last + 1..rank)
310 .map(|i| in_shape.dim(i).unwrap_static())
311 .product::<usize>()
312 .max(1);
313 let src = node_offset(arena, node.inputs[0]);
314 let dst = node_offset(arena, node.id);
315 if node.shape.dtype() == rlx_ir::DType::F64 && matches!(op, ReduceOp::Sum) {
316 Thunk::ReduceSumF64 {
317 src,
318 dst,
319 outer: outer as u32,
320 reduced: reduced as u32,
321 inner: inner as u32,
322 }
323 } else {
324 Thunk::Reduce {
325 src,
326 dst,
327 outer: outer as u32,
328 reduced: reduced as u32,
329 inner: inner as u32,
330 op: *op,
331 }
332 }
333 }
334 }
335}
336
337#[allow(unused_variables)]
338pub(crate) fn compile_cumsum_backward(
339 node: &rlx_ir::Node,
340 graph: &Graph,
341 arena: &crate::arena::Arena,
342 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
343 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
344 rng: rlx_ir::RngOptions,
345) -> Thunk {
346 let Op::CumsumBackward { exclusive, .. } = &node.op else {
347 unreachable!()
348 };
349 {
350 let dy_shape = &graph.node(node.inputs[0]).shape;
351 let rank = dy_shape.rank();
352 let cols = dy_shape.dim(rank - 1).unwrap_static();
353 let rows = dy_shape.num_elements().unwrap() / cols;
354 Thunk::CumsumBackward {
355 dy: node_offset(arena, node.inputs[0]),
356 dx: node_offset(arena, node.id),
357 rows: rows as u32,
358 cols: cols as u32,
359 exclusive: *exclusive,
360 }
361 }
362}
363
364#[allow(unused_variables)]
365pub(crate) fn compile_softmax_cross_entropy(
366 node: &rlx_ir::Node,
367 graph: &Graph,
368 arena: &crate::arena::Arena,
369 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
370 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
371 rng: rlx_ir::RngOptions,
372) -> Thunk {
373 let Op::SoftmaxCrossEntropy = &node.op else {
374 unreachable!()
375 };
376 {
377 let logits_shape = &graph.node(node.inputs[0]).shape;
378 if logits_shape.rank() == 2 {
379 Thunk::SoftmaxCrossEntropyDense {
380 logits: node_offset(arena, node.inputs[0]),
381 targets: node_offset(arena, node.inputs[1]),
382 dst: node_offset(arena, node.id),
383 n: logits_shape.dim(0).unwrap_static() as u32,
384 c: logits_shape.dim(1).unwrap_static() as u32,
385 }
386 } else {
387 Thunk::Nop
388 }
389 }
390}
391
392#[allow(unused_variables)]
393pub(crate) fn compile_softmax_cross_entropy_with_logits(
394 node: &rlx_ir::Node,
395 graph: &Graph,
396 arena: &crate::arena::Arena,
397 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
398 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
399 rng: rlx_ir::RngOptions,
400) -> Thunk {
401 let Op::SoftmaxCrossEntropyWithLogits = &node.op else {
402 unreachable!()
403 };
404 {
405 let logits_shape = &graph.node(node.inputs[0]).shape;
406 if logits_shape.rank() == 2 {
407 Thunk::SoftmaxCrossEntropy {
408 logits: node_offset(arena, node.inputs[0]),
409 labels: node_offset(arena, node.inputs[1]),
410 dst: node_offset(arena, node.id),
411 n: logits_shape.dim(0).unwrap_static() as u32,
412 c: logits_shape.dim(1).unwrap_static() as u32,
413 }
414 } else {
415 Thunk::Nop
416 }
417 }
418}
419
420#[allow(unused_variables)]
421pub(crate) fn compile_softmax_cross_entropy_backward(
422 node: &rlx_ir::Node,
423 graph: &Graph,
424 arena: &crate::arena::Arena,
425 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
426 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
427 rng: rlx_ir::RngOptions,
428) -> Thunk {
429 let Op::SoftmaxCrossEntropyBackward = &node.op else {
430 unreachable!()
431 };
432 {
433 let logits_shape = &graph.node(node.inputs[0]).shape;
434 if logits_shape.rank() == 2 {
435 Thunk::SoftmaxCrossEntropyBackward {
436 logits: node_offset(arena, node.inputs[0]),
437 labels: node_offset(arena, node.inputs[1]),
438 d_loss: node_offset(arena, node.inputs[2]),
439 dlogits: node_offset(arena, node.id),
440 n: logits_shape.dim(0).unwrap_static() as u32,
441 c: logits_shape.dim(1).unwrap_static() as u32,
442 }
443 } else {
444 Thunk::Nop
445 }
446 }
447}
448
449#[inline(always)]
450pub(crate) fn exec_reduce_sum_f64(t: &Thunk, base: *mut u8) {
451 let Thunk::ReduceSumF64 {
452 src,
453 dst,
454 outer,
455 reduced,
456 inner,
457 } = t
458 else {
459 unreachable!()
460 };
461 {
462 let (o, r, n) = (*outer as usize, *reduced as usize, *inner as usize);
463 unsafe {
464 let inp = sl_f64(*src, base, o * r * n);
465 let out = sl_mut_f64(*dst, base, o * n);
466 reduce_sum_f64(inp, out, o, r, n);
467 }
468 }
469}
470
471#[inline(always)]
472pub(crate) fn exec_softmax(t: &Thunk, base: *mut u8) {
473 let Thunk::Softmax { data, rows, cols } = t else {
474 unreachable!()
475 };
476 {
477 let (rows, cols) = (*rows as usize, *cols as usize);
478 unsafe {
479 crate::kernels::neon_softmax(sl_mut(*data, base, rows * cols), rows, cols);
480 }
481 }
482}
483
484#[inline(always)]
485pub(crate) fn exec_cumsum(t: &Thunk, base: *mut u8) {
486 let Thunk::Cumsum {
487 src,
488 dst,
489 rows,
490 cols,
491 exclusive,
492 dtype,
493 } = t
494 else {
495 unreachable!()
496 };
497 unsafe {
498 cumsum_typed(
499 base,
500 *src,
501 *dst,
502 *rows as usize,
503 *cols as usize,
504 *exclusive,
505 *dtype,
506 );
507 }
508}
509
510pub(crate) unsafe fn cumsum_typed(
515 base: *mut u8,
516 src: usize,
517 dst: usize,
518 rows: usize,
519 cols: usize,
520 exclusive: bool,
521 dtype: rlx_ir::DType,
522) {
523 macro_rules! scan {
524 ($sl:expr, $slm:expr, $zero:expr) => {{
525 let s = $sl(src, base, rows * cols);
526 let d = $slm(dst, base, rows * cols);
527 for r in 0..rows {
528 let mut acc = $zero;
529 for c in 0..cols {
530 if exclusive {
531 d[r * cols + c] = acc;
532 acc += s[r * cols + c];
533 } else {
534 acc += s[r * cols + c];
535 d[r * cols + c] = acc;
536 }
537 }
538 }
539 }};
540 }
541 match dtype {
542 rlx_ir::DType::I64 => scan!(sl_i64, sl_mut_i64, 0i64),
543 rlx_ir::DType::I32 => scan!(sl_i32, sl_mut_i32, 0i32),
544 _ => scan!(sl, sl_mut, 0.0f32),
545 }
546}
547
548#[inline(always)]
549pub(crate) fn exec_sample(t: &Thunk, base: *mut u8) {
550 let Thunk::Sample {
551 logits,
552 dst,
553 batch,
554 vocab,
555 top_k,
556 top_p,
557 temperature,
558 seed,
559 } = t
560 else {
561 unreachable!()
562 };
563 unsafe {
564 execute_sample_f32(
565 *logits,
566 *dst,
567 *batch as usize,
568 *vocab as usize,
569 *top_k as usize,
570 *top_p,
571 *temperature,
572 *seed,
573 base,
574 );
575 }
576}
577
578#[inline(always)]
579pub(crate) fn exec_reduce(t: &Thunk, base: *mut u8) {
580 let Thunk::Reduce {
581 src,
582 dst,
583 outer,
584 reduced,
585 inner,
586 op,
587 } = t
588 else {
589 unreachable!()
590 };
591 {
592 let outer = *outer as usize;
593 let reduced = *reduced as usize;
594 let inner = *inner as usize;
595 let in_total = outer * reduced * inner;
596 let out_total = outer * inner;
597 unsafe {
598 let inp = sl(*src, base, in_total);
599 let out = sl_mut(*dst, base, out_total);
600 let reduce_one = |oi: usize| -> f32 {
604 let o = oi / inner;
605 let i = oi % inner;
606 let mut acc = match op {
607 ReduceOp::Max => f32::NEG_INFINITY,
608 ReduceOp::Min => f32::INFINITY,
609 ReduceOp::Prod => 1.0f32,
610 _ => 0.0f32, };
612 for r in 0..reduced {
613 let v = inp[o * reduced * inner + r * inner + i];
614 acc = match op {
615 ReduceOp::Sum | ReduceOp::Mean => acc + v,
616 ReduceOp::Max => acc.max(v),
617 ReduceOp::Min => acc.min(v),
618 ReduceOp::Prod => acc * v,
619 };
620 }
621 if matches!(op, ReduceOp::Mean) {
622 acc /= reduced as f32;
623 }
624 acc
625 };
626 if fast_conv_enabled() && crate::pool::should_parallelize(in_total) && out_total > 1 {
627 let out_addr = out.as_mut_ptr() as usize;
628 crate::pool::par_for(
629 out_total,
630 crate::pool::outer_chunk(out_total),
631 &|off, cnt| {
632 for oi in off..off + cnt {
633 *((out_addr as *mut f32).add(oi)) = reduce_one(oi);
634 }
635 },
636 );
637 } else {
638 for oi in 0..out_total {
639 out[oi] = reduce_one(oi);
640 }
641 }
642 }
643 }
644}
645
646#[inline(always)]
647pub(crate) fn exec_arg_reduce(t: &Thunk, base: *mut u8) {
648 let Thunk::ArgReduce {
649 src,
650 dst,
651 outer,
652 reduced,
653 inner,
654 is_max,
655 } = t
656 else {
657 unreachable!()
658 };
659 {
660 let outer = *outer as usize;
661 let reduced = *reduced as usize;
662 let inner = *inner as usize;
663 let in_total = outer * reduced * inner;
664 let out_total = outer * inner;
665 unsafe {
666 let inp = sl(*src, base, in_total);
667 let out = sl_mut(*dst, base, out_total);
668 for o in 0..outer {
669 for i in 0..inner {
670 let mut best = inp[o * reduced * inner + i];
671 let mut best_idx = 0usize;
672 for r in 1..reduced {
673 let v = inp[o * reduced * inner + r * inner + i];
674 let better = if *is_max { v > best } else { v < best };
675 if better {
676 best = v;
677 best_idx = r;
678 }
679 }
680 out[o * inner + i] = best_idx as f32;
681 }
682 }
683 }
684 }
685}
686
687#[inline(always)]
688pub(crate) fn exec_cumsum_backward(t: &Thunk, base: *mut u8) {
689 let Thunk::CumsumBackward {
690 dy,
691 dx,
692 rows,
693 cols,
694 exclusive,
695 } = t
696 else {
697 unreachable!()
698 };
699 {
700 let (rows, cols) = (*rows as usize, *cols as usize);
701 unsafe {
702 let dys = sl(*dy, base, rows * cols);
703 let out = sl_mut(*dx, base, rows * cols);
704 for r in 0..rows {
705 crate::training_bwd::cumsum_backward_row(
706 &dys[r * cols..(r + 1) * cols],
707 &mut out[r * cols..(r + 1) * cols],
708 *exclusive,
709 );
710 }
711 }
712 }
713}
714
715#[inline(always)]
716pub(crate) fn exec_softmax_cross_entropy_dense(t: &Thunk, base: *mut u8) {
717 let Thunk::SoftmaxCrossEntropyDense {
718 logits,
719 targets,
720 dst,
721 n,
722 c,
723 } = t
724 else {
725 unreachable!()
726 };
727 {
728 let n = *n as usize;
729 let c = *c as usize;
730 unsafe {
731 let lg = sl(*logits, base, n * c);
732 let tg = sl(*targets, base, n * c);
733 let out = sl_mut(*dst, base, n);
734 for ni in 0..n {
735 let row = &lg[ni * c..(ni + 1) * c];
736 let trow = &tg[ni * c..(ni + 1) * c];
737 let mut m = f32::NEG_INFINITY;
739 for &v in row {
740 if v > m {
741 m = v;
742 }
743 }
744 let mut sum = 0f32;
745 for &v in row {
746 sum += (v - m).exp();
747 }
748 let lse = m + sum.ln();
749 let mut dot = 0f32;
751 for k in 0..c {
752 dot += trow[k] * row[k];
753 }
754 out[ni] = lse - dot;
755 }
756 }
757 }
758}
759
760#[inline(always)]
761pub(crate) fn exec_softmax_cross_entropy(t: &Thunk, base: *mut u8) {
762 let Thunk::SoftmaxCrossEntropy {
763 logits,
764 labels,
765 dst,
766 n,
767 c,
768 } = t
769 else {
770 unreachable!()
771 };
772 {
773 let n = *n as usize;
774 let c = *c as usize;
775 unsafe {
776 let lg = sl(*logits, base, n * c);
777 let lb = sl(*labels, base, n);
778 let out = sl_mut(*dst, base, n);
779 for ni in 0..n {
780 let row = &lg[ni * c..(ni + 1) * c];
781 let mut m = f32::NEG_INFINITY;
783 for &v in row {
784 if v > m {
785 m = v;
786 }
787 }
788 let mut sum = 0f32;
789 for &v in row {
790 sum += (v - m).exp();
791 }
792 let lse = m + sum.ln();
793 let label_idx = lb[ni] as usize;
794 out[ni] = lse - row[label_idx];
796 }
797 }
798 }
799}
800
801#[inline(always)]
802pub(crate) fn exec_softmax_cross_entropy_backward(t: &Thunk, base: *mut u8) {
803 let Thunk::SoftmaxCrossEntropyBackward {
804 logits,
805 labels,
806 d_loss,
807 dlogits,
808 n,
809 c,
810 } = t
811 else {
812 unreachable!()
813 };
814 {
815 let n = *n as usize;
816 let c = *c as usize;
817 unsafe {
818 let lg = sl(*logits, base, n * c);
819 let lb = sl(*labels, base, n);
820 let dl = sl(*d_loss, base, n);
821 let out = sl_mut(*dlogits, base, n * c);
822 for ni in 0..n {
823 let row = &lg[ni * c..(ni + 1) * c];
824 let label_idx = lb[ni] as usize;
825 let scale = dl[ni];
826 let mut m = f32::NEG_INFINITY;
827 for &v in row {
828 if v > m {
829 m = v;
830 }
831 }
832 let mut sum = 0f32;
833 for &v in row {
834 sum += (v - m).exp();
835 }
836 let inv_sum = 1.0 / sum;
837 let dst_row = &mut out[ni * c..(ni + 1) * c];
838 for k in 0..c {
839 let p = (row[k] - m).exp() * inv_sum;
840 let one_hot = if k == label_idx { 1.0 } else { 0.0 };
841 dst_row[k] = (p - one_hot) * scale;
842 }
843 }
844 }
845 }
846}
847
848pub unsafe fn execute_argreduce_f32(
853 src: usize,
854 dst: usize,
855 outer: usize,
856 reduced: usize,
857 inner: usize,
858 is_max: bool,
859 base: *mut u8,
860) {
861 let bptr = base as usize;
862 unsafe {
863 let inp = std::slice::from_raw_parts((bptr + src) as *const f32, outer * reduced * inner);
864 let out = std::slice::from_raw_parts_mut((bptr + dst) as *mut f32, outer * inner);
865 for o in 0..outer {
866 for i in 0..inner {
867 let mut best = inp[o * reduced * inner + i];
868 let mut best_idx = 0usize;
869 for r in 1..reduced {
870 let v = inp[o * reduced * inner + r * inner + i];
871 let better = if is_max { v > best } else { v < best };
872 if better {
873 best = v;
874 best_idx = r;
875 }
876 }
877 out[o * inner + i] = best_idx as f32;
878 }
879 }
880 }
881}
882
883pub unsafe fn execute_sample_f32(
888 logits: usize,
889 dst: usize,
890 batch: usize,
891 vocab: usize,
892 top_k: usize,
893 top_p: f32,
894 temperature: f32,
895 seed: u64,
896 base: *mut u8,
897) {
898 let (b, v) = (batch, vocab);
899 let k = top_k.min(v);
900 unsafe {
901 let lg = sl(logits, base, b * v);
902 let out = sl_mut(dst, base, b);
903 let mut rng = rlx_ir::Philox4x32::new(if seed == 0 { 0xDEADBEEF } else { seed });
904 for bi in 0..b {
905 let row = &lg[bi * v..(bi + 1) * v];
906 out[bi] = sample_row(row, k, top_p, temperature, &mut rng) as f32;
907 }
908 }
909}
910
911pub unsafe fn execute_cumsum_backward_f32(
912 dy: usize,
913 dx: usize,
914 rows: u32,
915 cols: u32,
916 exclusive: bool,
917 base: *mut u8,
918) {
919 let (rows, cols) = (rows as usize, cols as usize);
920 let dys = sl(dy, base, rows * cols);
921 let out = sl_mut(dx, base, rows * cols);
922 for r in 0..rows {
923 crate::training_bwd::cumsum_backward_row(
924 &dys[r * cols..(r + 1) * cols],
925 &mut out[r * cols..(r + 1) * cols],
926 exclusive,
927 );
928 }
929}
930
931pub(crate) fn reduce_sum_f64(
934 inp: &[f64],
935 out: &mut [f64],
936 outer: usize,
937 reduced: usize,
938 inner: usize,
939) {
940 for o in 0..outer {
941 for n in 0..inner {
942 let mut acc = 0.0_f64;
943 for r in 0..reduced {
944 acc += inp[o * reduced * inner + r * inner + n];
945 }
946 out[o * inner + n] = acc;
947 }
948 }
949}
950
951pub unsafe fn fill_rng_normal_arena(
957 dst_off: usize,
958 len: usize,
959 mean: f32,
960 scale: f32,
961 key: u64,
962 op_seed: Option<f32>,
963 opts: rlx_ir::RngOptions,
964 arena: *mut u8,
965) {
966 if len == 0 {
967 return;
968 }
969 unsafe {
970 let out = std::slice::from_raw_parts_mut((arena.add(dst_off)) as *mut f32, len);
971 rlx_ir::fill_normal_like(out, mean, scale, opts, key, op_seed);
972 }
973}
974
975pub unsafe fn fill_rng_uniform_arena(
976 dst_off: usize,
977 len: usize,
978 low: f32,
979 high: f32,
980 key: u64,
981 op_seed: Option<f32>,
982 opts: rlx_ir::RngOptions,
983 arena: *mut u8,
984) {
985 if len == 0 {
986 return;
987 }
988 unsafe {
989 let out = std::slice::from_raw_parts_mut((arena.add(dst_off)) as *mut f32, len);
990 rlx_ir::fill_uniform_like(out, low, high, opts, key, op_seed);
991 }
992}