1#![allow(unsafe_op_in_unsafe_fn)]
5use crate::thunk::*;
6
7pub unsafe fn execute_scan_host(
18 base: *mut u8,
19 plan: &ScanBodyPlan,
20 outer_init_off: usize,
21 outer_final_off: usize,
22 length: u32,
23 save_trajectory: bool,
24 xs_outer: &[(usize, usize)],
25 bcast_outer: &[(usize, usize)],
26) {
27 let cb = plan.carry_bytes;
28 let mut body_buf: Vec<u8> = plan.body_init.clone();
29 unsafe {
30 std::ptr::copy_nonoverlapping(
31 base.add(outer_init_off),
32 body_buf.as_mut_ptr().add(plan.body_input_off),
33 cb,
34 );
35 for (i, &(outer_b_off, total)) in bcast_outer.iter().enumerate() {
36 std::ptr::copy_nonoverlapping(
37 base.add(outer_b_off),
38 body_buf.as_mut_ptr().add(plan.bcast_body_offs[i]),
39 total,
40 );
41 }
42 }
43 for t in 0..length as usize {
44 for (i, &(outer_xs_off, psb)) in xs_outer.iter().enumerate() {
45 unsafe {
46 std::ptr::copy_nonoverlapping(
47 base.add(outer_xs_off + t * psb),
48 body_buf.as_mut_ptr().add(plan.xs_body_offs[i]),
49 psb,
50 );
51 }
52 }
53 execute_thunks(&plan.body, &mut body_buf);
54 if save_trajectory {
55 unsafe {
56 std::ptr::copy_nonoverlapping(
57 body_buf.as_ptr().add(plan.body_output_off),
58 base.add(outer_final_off + t * cb),
59 cb,
60 );
61 }
62 }
63 if plan.body_output_off != plan.body_input_off {
64 body_buf.copy_within(
65 plan.body_output_off..plan.body_output_off + cb,
66 plan.body_input_off,
67 );
68 }
69 }
70 if !save_trajectory {
71 unsafe {
72 std::ptr::copy_nonoverlapping(
73 body_buf.as_ptr().add(plan.body_output_off),
74 base.add(outer_final_off),
75 cb,
76 );
77 }
78 }
79}
80
81#[allow(unused_variables)]
82pub(crate) fn compile_selective_scan(
83 node: &rlx_ir::Node,
84 graph: &Graph,
85 arena: &crate::arena::Arena,
86 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
87 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
88 rng: rlx_ir::RngOptions,
89) -> Thunk {
90 let Op::SelectiveScan { state_size } = &node.op else {
91 unreachable!()
92 };
93 {
94 let in_shape = &graph.node(node.inputs[0]).shape;
95 let (batch, seq, hidden) = (
96 in_shape.dim(0).unwrap_static(),
97 in_shape.dim(1).unwrap_static(),
98 in_shape.dim(2).unwrap_static(),
99 );
100 Thunk::SelectiveScan {
101 x: node_offset(arena, node.inputs[0]),
102 delta: node_offset(arena, node.inputs[1]),
103 a: node_offset(arena, node.inputs[2]),
104 b: node_offset(arena, node.inputs[3]),
105 c: node_offset(arena, node.inputs[4]),
106 dst: node_offset(arena, node.id),
107 batch: batch as u32,
108 seq: seq as u32,
109 hidden: hidden as u32,
110 state_size: *state_size as u32,
111 }
112 }
113}
114
115#[allow(unused_variables)]
116pub(crate) fn compile_gated_delta_net(
117 node: &rlx_ir::Node,
118 graph: &Graph,
119 arena: &crate::arena::Arena,
120 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
121 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
122 rng: rlx_ir::RngOptions,
123) -> Thunk {
124 let Op::GatedDeltaNet {
125 state_size,
126 carry_state,
127 gate_per_channel,
128 } = &node.op
129 else {
130 unreachable!()
131 };
132 {
133 let q_shape = &graph.node(node.inputs[0]).shape;
134 let (batch, seq, heads) = (
135 q_shape.dim(0).unwrap_static(),
136 q_shape.dim(1).unwrap_static(),
137 q_shape.dim(2).unwrap_static(),
138 );
139 let state_off = if *carry_state {
140 node_offset(arena, node.inputs[5])
141 } else {
142 0
143 };
144 Thunk::GatedDeltaNet {
145 q: node_offset(arena, node.inputs[0]),
146 k: node_offset(arena, node.inputs[1]),
147 v: node_offset(arena, node.inputs[2]),
148 g: node_offset(arena, node.inputs[3]),
149 beta: node_offset(arena, node.inputs[4]),
150 state: state_off,
151 dst: node_offset(arena, node.id),
152 batch: batch as u32,
153 seq: seq as u32,
154 heads: heads as u32,
155 state_size: *state_size as u32,
156 gate_per_channel: *gate_per_channel,
157 carry_state: *carry_state,
158 }
159 }
160}
161
162#[allow(unused_variables)]
163pub(crate) fn compile_lstm(
164 node: &rlx_ir::Node,
165 graph: &Graph,
166 arena: &crate::arena::Arena,
167 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
168 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
169 rng: rlx_ir::RngOptions,
170) -> Thunk {
171 let Op::Lstm {
172 hidden_size,
173 num_layers,
174 bidirectional,
175 carry,
176 } = &node.op
177 else {
178 unreachable!()
179 };
180 {
181 let x_shape = &graph.node(node.inputs[0]).shape;
182 let (batch, seq, input_size) = (
183 x_shape.dim(0).unwrap_static(),
184 x_shape.dim(1).unwrap_static(),
185 x_shape.dim(2).unwrap_static(),
186 );
187 let ex = rnn_expected_lens(
188 4,
189 batch,
190 seq,
191 input_size,
192 *hidden_size,
193 *num_layers,
194 *bidirectional,
195 );
196 let nelem = |i: usize| graph.node(node.inputs[i]).shape.num_elements().unwrap_or(0);
197 check_rnn_lens(
198 "lstm",
199 &[
200 ("w_ih", nelem(1), ex.w_ih),
201 ("w_hh", nelem(2), ex.w_hh),
202 ("bias", nelem(3), ex.bias),
203 ],
204 )
205 .unwrap_or_else(|e| panic!("{e}"));
206 let (h0, c0) = if *carry {
207 (
208 node_offset(arena, node.inputs[4]),
209 node_offset(arena, node.inputs[5]),
210 )
211 } else {
212 (0, 0)
213 };
214 Thunk::Lstm {
215 x: node_offset(arena, node.inputs[0]),
216 w_ih: node_offset(arena, node.inputs[1]),
217 w_hh: node_offset(arena, node.inputs[2]),
218 bias: node_offset(arena, node.inputs[3]),
219 h0,
220 c0,
221 dst: node_offset(arena, node.id),
222 batch: batch as u32,
223 seq: seq as u32,
224 input_size: input_size as u32,
225 hidden: *hidden_size as u32,
226 num_layers: *num_layers as u32,
227 bidirectional: *bidirectional,
228 carry: *carry,
229 }
230 }
231}
232
233#[allow(unused_variables)]
234pub(crate) fn compile_gru(
235 node: &rlx_ir::Node,
236 graph: &Graph,
237 arena: &crate::arena::Arena,
238 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
239 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
240 rng: rlx_ir::RngOptions,
241) -> Thunk {
242 let Op::Gru {
243 hidden_size,
244 num_layers,
245 bidirectional,
246 carry,
247 } = &node.op
248 else {
249 unreachable!()
250 };
251 {
252 let x_shape = &graph.node(node.inputs[0]).shape;
253 let (batch, seq, input_size) = (
254 x_shape.dim(0).unwrap_static(),
255 x_shape.dim(1).unwrap_static(),
256 x_shape.dim(2).unwrap_static(),
257 );
258 let ex = rnn_expected_lens(
260 3,
261 batch,
262 seq,
263 input_size,
264 *hidden_size,
265 *num_layers,
266 *bidirectional,
267 );
268 let nelem = |i: usize| graph.node(node.inputs[i]).shape.num_elements().unwrap_or(0);
269 check_rnn_lens(
270 "gru",
271 &[
272 ("w_ih", nelem(1), ex.w_ih),
273 ("w_hh", nelem(2), ex.w_hh),
274 ("b_ih", nelem(3), ex.bias),
275 ("b_hh", nelem(4), ex.bias),
276 ],
277 )
278 .unwrap_or_else(|e| panic!("{e}"));
279 let h0 = if *carry {
280 node_offset(arena, node.inputs[5])
281 } else {
282 0
283 };
284 Thunk::Gru {
285 x: node_offset(arena, node.inputs[0]),
286 w_ih: node_offset(arena, node.inputs[1]),
287 w_hh: node_offset(arena, node.inputs[2]),
288 b_ih: node_offset(arena, node.inputs[3]),
289 b_hh: node_offset(arena, node.inputs[4]),
290 h0,
291 dst: node_offset(arena, node.id),
292 batch: batch as u32,
293 seq: seq as u32,
294 input_size: input_size as u32,
295 hidden: *hidden_size as u32,
296 num_layers: *num_layers as u32,
297 bidirectional: *bidirectional,
298 carry: *carry,
299 }
300 }
301}
302
303#[allow(unused_variables)]
304pub(crate) fn compile_rnn(
305 node: &rlx_ir::Node,
306 graph: &Graph,
307 arena: &crate::arena::Arena,
308 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
309 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
310 rng: rlx_ir::RngOptions,
311) -> Thunk {
312 let Op::Rnn {
313 hidden_size,
314 num_layers,
315 bidirectional,
316 carry,
317 relu,
318 } = &node.op
319 else {
320 unreachable!()
321 };
322 {
323 let x_shape = &graph.node(node.inputs[0]).shape;
324 let (batch, seq, input_size) = (
325 x_shape.dim(0).unwrap_static(),
326 x_shape.dim(1).unwrap_static(),
327 x_shape.dim(2).unwrap_static(),
328 );
329 let ex = rnn_expected_lens(
331 1,
332 batch,
333 seq,
334 input_size,
335 *hidden_size,
336 *num_layers,
337 *bidirectional,
338 );
339 let nelem = |i: usize| graph.node(node.inputs[i]).shape.num_elements().unwrap_or(0);
340 check_rnn_lens(
341 "rnn",
342 &[
343 ("w_ih", nelem(1), ex.w_ih),
344 ("w_hh", nelem(2), ex.w_hh),
345 ("bias", nelem(3), ex.bias),
346 ],
347 )
348 .unwrap_or_else(|e| panic!("{e}"));
349 let h0 = if *carry {
350 node_offset(arena, node.inputs[4])
351 } else {
352 0
353 };
354 Thunk::Rnn {
355 x: node_offset(arena, node.inputs[0]),
356 w_ih: node_offset(arena, node.inputs[1]),
357 w_hh: node_offset(arena, node.inputs[2]),
358 bias: node_offset(arena, node.inputs[3]),
359 h0,
360 dst: node_offset(arena, node.id),
361 batch: batch as u32,
362 seq: seq as u32,
363 input_size: input_size as u32,
364 hidden: *hidden_size as u32,
365 num_layers: *num_layers as u32,
366 bidirectional: *bidirectional,
367 carry: *carry,
368 relu: *relu,
369 }
370 }
371}
372
373#[allow(unused_variables)]
374pub(crate) fn compile_mamba2(
375 node: &rlx_ir::Node,
376 graph: &Graph,
377 arena: &crate::arena::Arena,
378 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
379 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
380 rng: rlx_ir::RngOptions,
381) -> Thunk {
382 let Op::Mamba2 {
383 head_dim,
384 state_size,
385 } = &node.op
386 else {
387 unreachable!()
388 };
389 {
390 let x_shape = &graph.node(node.inputs[0]).shape;
392 Thunk::Mamba2 {
393 x: node_offset(arena, node.inputs[0]),
394 dt: node_offset(arena, node.inputs[1]),
395 a: node_offset(arena, node.inputs[2]),
396 b: node_offset(arena, node.inputs[3]),
397 c: node_offset(arena, node.inputs[4]),
398 dst: node_offset(arena, node.id),
399 batch: x_shape.dim(0).unwrap_static() as u32,
400 seq: x_shape.dim(1).unwrap_static() as u32,
401 heads: x_shape.dim(2).unwrap_static() as u32,
402 head_dim: *head_dim as u32,
403 state_size: *state_size as u32,
404 }
405 }
406}
407
408#[allow(unused_variables)]
409pub(crate) fn compile_scan(
410 node: &rlx_ir::Node,
411 graph: &Graph,
412 arena: &crate::arena::Arena,
413 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
414 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
415 rng: rlx_ir::RngOptions,
416) -> Thunk {
417 let Op::Scan {
418 body,
419 length,
420 save_trajectory,
421 num_bcast,
422 num_xs,
423 num_checkpoints,
424 } = &node.op
425 else {
426 unreachable!()
427 };
428 {
429 assert!(
430 *num_checkpoints == 0 || *num_checkpoints <= *length,
431 "Op::Scan: num_checkpoints={} must be 0 or ≤ length={}",
432 *num_checkpoints,
433 *length
434 );
435 if *num_checkpoints != 0 && *num_checkpoints != *length {
436 assert!(
437 *save_trajectory,
438 "Op::Scan: num_checkpoints<length only meaningful when save_trajectory=true"
439 );
440 }
441 let body_plan = rlx_opt::memory::plan_memory(body);
452 let _body_arena_size = body_plan.arena_size;
453 let body_offsets: HashMap<NodeId, usize> = body_plan
456 .assignments
457 .iter()
458 .map(|(id, slot)| (*id, slot.offset))
459 .collect();
460
461 let mut body_inputs: Vec<NodeId> = body
464 .nodes()
465 .iter()
466 .filter(|n| matches!(n.op, Op::Input { .. }))
467 .map(|n| n.id)
468 .collect();
469 body_inputs.sort();
470 let n_body_inputs = body_inputs.len();
471 let expected = 1 + *num_bcast as usize + *num_xs as usize;
472 if n_body_inputs != expected {
473 let names: Vec<String> = body
474 .nodes()
475 .iter()
476 .filter_map(|n| match &n.op {
477 Op::Input { name } => Some(format!("{}={}", n.id, name)),
478 _ => None,
479 })
480 .collect();
481 panic!(
482 "Op::Scan body has {} Op::Input nodes; expected {} \
483 (1 carry + {} bcast + {} xs). Inputs by NodeId: [{}]",
484 n_body_inputs,
485 expected,
486 *num_bcast,
487 *num_xs,
488 names.join(", ")
489 );
490 }
491
492 let body_input_id = body_inputs[0];
493 let body_input_off = body_offsets[&body_input_id];
494 let body_output_id = body
495 .outputs
496 .first()
497 .copied()
498 .expect("Op::Scan body must declare one output");
499 let body_output_off = body_offsets[&body_output_id];
500
501 let mut body_arena = crate::arena::Arena::from_plan(body_plan);
502 for n in body.nodes() {
505 if let Op::Constant { data } = &n.op
506 && body_arena.has_buffer(n.id)
507 && !data.is_empty()
508 {
509 match n.shape.dtype() {
510 rlx_ir::DType::F64 => {
511 let off = body_arena.byte_offset(n.id);
512 let buf = body_arena.raw_buf_mut();
513 let nbytes = (buf.len() - off).min(data.len());
514 buf[off..off + nbytes].copy_from_slice(&data[..nbytes]);
515 }
516 _ => {
517 let buf = body_arena.slice_mut(n.id);
518 let n_floats = data.len() / 4;
519 let n_lim = buf.len().min(n_floats);
520 for i in 0..n_lim {
521 let bytes = [
522 data[i * 4],
523 data[i * 4 + 1],
524 data[i * 4 + 2],
525 data[i * 4 + 3],
526 ];
527 buf[i] = f32::from_le_bytes(bytes);
528 }
529 }
530 }
531 }
532 }
533 let body_init = body_arena.raw_buf().to_vec();
534 let body_schedule = compile_thunks_with_rng(body, &body_arena, rng);
535
536 let carry_bytes = if *save_trajectory {
541 let total = node
542 .shape
543 .size_bytes()
544 .expect("Op::Scan trajectory output must have static shape");
545 total / *length as usize
546 } else {
547 node.shape
548 .size_bytes()
549 .expect("Op::Scan carry must have static shape")
550 };
551
552 let mut bcast_inputs: Vec<(usize, usize, u32)> = Vec::with_capacity(*num_bcast as usize);
557 for i in 0..*num_bcast as usize {
558 let body_b_id = body_inputs[1 + i];
559 let body_b_off = body_offsets[&body_b_id];
560 let outer_b_id = node.inputs[1 + i];
561 let outer_b_off = node_offset(arena, outer_b_id);
562 let outer_b_shape = &graph.node(outer_b_id).shape;
563 let total = outer_b_shape
564 .size_bytes()
565 .expect("Op::Scan bcast must have static shape");
566 bcast_inputs.push((body_b_off, outer_b_off, total as u32));
567 }
568
569 let mut xs_inputs: Vec<(usize, usize, u32)> = Vec::with_capacity(*num_xs as usize);
573 let xs_base = 1 + *num_bcast as usize;
574 for i in 0..*num_xs as usize {
575 let body_x_id = body_inputs[xs_base + i];
576 let body_x_off = body_offsets[&body_x_id];
577 let outer_xs_id = node.inputs[xs_base + i];
578 let outer_xs_off = node_offset(arena, outer_xs_id);
579 let outer_xs_shape = &graph.node(outer_xs_id).shape;
580 let total = outer_xs_shape
581 .size_bytes()
582 .expect("Op::Scan xs must have static shape");
583 let per_step = total / *length as usize;
584 xs_inputs.push((body_x_off, outer_xs_off, per_step as u32));
585 }
586
587 Thunk::Scan {
588 body: Arc::new(body_schedule),
589 body_init: Arc::new(body_init),
590 body_input_off,
591 body_output_off,
592 outer_init_off: node_offset(arena, node.inputs[0]),
593 outer_final_off: node_offset(arena, node.id),
594 length: *length,
595 carry_bytes: carry_bytes as u32,
596 save_trajectory: *save_trajectory,
597 xs_inputs: Arc::new(xs_inputs),
598 bcast_inputs: Arc::new(bcast_inputs),
599 num_checkpoints: *num_checkpoints,
600 }
601 }
602}
603
604#[allow(unused_variables)]
605pub(crate) fn compile_scan_backward(
606 node: &rlx_ir::Node,
607 graph: &Graph,
608 arena: &crate::arena::Arena,
609 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
610 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
611 rng: rlx_ir::RngOptions,
612) -> Thunk {
613 let Op::ScanBackward {
614 body_vjp,
615 length,
616 save_trajectory,
617 num_xs,
618 num_checkpoints,
619 forward_body,
620 } = &node.op
621 else {
622 unreachable!()
623 };
624 {
625 let is_recursive = *num_checkpoints != 0 && *num_checkpoints != *length;
626 if is_recursive {
627 assert!(
628 forward_body.is_some(),
629 "Op::ScanBackward with num_checkpoints<length requires forward_body"
630 );
631 }
632 let body_plan = rlx_opt::memory::plan_memory(body_vjp);
640 let body_offsets: HashMap<NodeId, usize> = body_plan
641 .assignments
642 .iter()
643 .map(|(id, slot)| (*id, slot.offset))
644 .collect();
645 let mut body_d_output_off: Option<usize> = None;
646 let mut body_other_inputs: Vec<(NodeId, usize)> = Vec::new();
647 for n in body_vjp.nodes() {
648 if let Op::Input { name } = &n.op {
649 let off = body_offsets[&n.id];
650 if name == "d_output" {
651 body_d_output_off = Some(off);
652 } else {
653 body_other_inputs.push((n.id, off));
654 }
655 }
656 }
657 body_other_inputs.sort_by_key(|(id, _)| *id);
658 let body_d_output_off =
659 body_d_output_off.expect("ScanBackward body_vjp missing 'd_output' Input");
660 let expected_others = 1 + *num_xs as usize;
661 assert_eq!(
662 body_other_inputs.len(),
663 expected_others,
664 "ScanBackward body_vjp has {} non-d_output Inputs; \
665 expected {} (1 carry + {} xs)",
666 body_other_inputs.len(),
667 expected_others,
668 num_xs
669 );
670 let body_carry_in_off = body_other_inputs[0].1;
671 let body_x_offs: Vec<usize> = body_other_inputs
672 .iter()
673 .skip(1)
674 .map(|(_, off)| *off)
675 .collect();
676 let body_dcarry_out_off = body_offsets[&body_vjp.outputs[0]];
677
678 let mut body_arena = crate::arena::Arena::from_plan(body_plan);
679 for n in body_vjp.nodes() {
681 if let Op::Constant { data } = &n.op
682 && body_arena.has_buffer(n.id)
683 && !data.is_empty()
684 {
685 match n.shape.dtype() {
686 rlx_ir::DType::F64 => {
687 let off = body_arena.byte_offset(n.id);
688 let buf = body_arena.raw_buf_mut();
689 let nb = (buf.len() - off).min(data.len());
690 buf[off..off + nb].copy_from_slice(&data[..nb]);
691 }
692 _ => {
693 let buf = body_arena.slice_mut(n.id);
694 let nf = data.len() / 4;
695 let nl = buf.len().min(nf);
696 for i in 0..nl {
697 let bytes = [
698 data[i * 4],
699 data[i * 4 + 1],
700 data[i * 4 + 2],
701 data[i * 4 + 3],
702 ];
703 buf[i] = f32::from_le_bytes(bytes);
704 }
705 }
706 }
707 }
708 }
709 let body_init = body_arena.raw_buf().to_vec();
710 let body_schedule = compile_thunks_with_rng(body_vjp, &body_arena, rng);
711
712 let carry_bytes = body_vjp
714 .node(body_vjp.outputs[0])
715 .shape
716 .size_bytes()
717 .expect("ScanBackward dcarry must be statically shaped");
718 let carry_elem_size = body_vjp
719 .node(body_vjp.outputs[0])
720 .shape
721 .dtype()
722 .size_bytes() as u32;
723
724 let mut outer_xs_offs: Vec<(usize, u32)> = Vec::with_capacity(*num_xs as usize);
727 for i in 0..*num_xs as usize {
728 let outer_xs_id = node.inputs[3 + i];
729 let outer_xs_off = node_offset(arena, outer_xs_id);
730 let outer_xs_shape = &graph.node(outer_xs_id).shape;
731 let total = outer_xs_shape
732 .size_bytes()
733 .expect("ScanBackward xs must have static shape");
734 let per_step = total / *length as usize;
735 outer_xs_offs.push((outer_xs_off, per_step as u32));
736 }
737
738 let (fb_schedule, fb_init, fb_carry_in_off, fb_output_off, fb_x_offs) = if is_recursive {
743 let fb = forward_body.as_ref().unwrap();
744 let fb_plan = rlx_opt::memory::plan_memory(fb);
745 let fb_offsets: HashMap<NodeId, usize> = fb_plan
746 .assignments
747 .iter()
748 .map(|(id, slot)| (*id, slot.offset))
749 .collect();
750 let mut fb_inputs: Vec<NodeId> = fb
751 .nodes()
752 .iter()
753 .filter(|n| matches!(n.op, Op::Input { .. }))
754 .map(|n| n.id)
755 .collect();
756 fb_inputs.sort();
757 let fb_carry = fb_offsets[&fb_inputs[0]];
758 let fb_xs: Vec<usize> = (1..fb_inputs.len())
759 .map(|i| fb_offsets[&fb_inputs[i]])
760 .collect();
761 let fb_out = fb_offsets[&fb.outputs[0]];
762 let mut fb_arena = crate::arena::Arena::from_plan(fb_plan);
763 for n in fb.nodes() {
764 if let Op::Constant { data } = &n.op
765 && fb_arena.has_buffer(n.id)
766 && !data.is_empty()
767 {
768 let off = fb_arena.byte_offset(n.id);
775 let buf = fb_arena.raw_buf_mut();
776 let nb = (buf.len() - off).min(data.len());
777 buf[off..off + nb].copy_from_slice(&data[..nb]);
778 }
779 }
780 let fb_init_bytes = fb_arena.raw_buf().to_vec();
781 let fb_sched = compile_thunks_with_rng(fb, &fb_arena, rng);
782 (
783 Some(Arc::new(fb_sched)),
784 Some(Arc::new(fb_init_bytes)),
785 fb_carry,
786 fb_out,
787 fb_xs,
788 )
789 } else {
790 (None, None, 0, 0, Vec::new())
791 };
792
793 Thunk::ScanBackward {
794 body_vjp: Arc::new(body_schedule),
795 body_init: Arc::new(body_init),
796 body_carry_in_off,
797 body_x_offs: Arc::new(body_x_offs),
798 body_d_output_off,
799 body_dcarry_out_off,
800 outer_init_off: node_offset(arena, node.inputs[0]),
801 outer_traj_off: node_offset(arena, node.inputs[1]),
802 outer_upstream_off: node_offset(arena, node.inputs[2]),
803 outer_xs_offs: Arc::new(outer_xs_offs),
804 outer_dinit_off: node_offset(arena, node.id),
805 length: *length,
806 carry_bytes: carry_bytes as u32,
807 carry_elem_size,
808 save_trajectory: *save_trajectory,
809 num_checkpoints: *num_checkpoints,
810 forward_body: fb_schedule,
811 forward_body_init: fb_init,
812 forward_body_carry_in_off: fb_carry_in_off,
813 forward_body_output_off: fb_output_off,
814 forward_body_x_offs: Arc::new(fb_x_offs),
815 }
816 }
817}
818
819#[allow(unused_variables)]
820pub(crate) fn compile_scan_backward_xs(
821 node: &rlx_ir::Node,
822 graph: &Graph,
823 arena: &crate::arena::Arena,
824 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
825 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
826 rng: rlx_ir::RngOptions,
827) -> Thunk {
828 let Op::ScanBackwardXs {
829 body_vjp,
830 length,
831 save_trajectory,
832 num_xs,
833 xs_idx,
834 num_checkpoints,
835 forward_body,
836 } = &node.op
837 else {
838 unreachable!()
839 };
840 {
841 assert!(
842 *num_checkpoints == 0 || *num_checkpoints <= *length,
843 "Op::ScanBackwardXs: num_checkpoints={} must be 0 or ≤ length={}",
844 *num_checkpoints,
845 *length
846 );
847 let is_recursive = *num_checkpoints != 0 && *num_checkpoints != *length;
848 if is_recursive {
849 assert!(
850 forward_body.is_some(),
851 "Op::ScanBackwardXs with num_checkpoints<length \
852 requires forward_body"
853 );
854 }
855 let body_plan = rlx_opt::memory::plan_memory(body_vjp);
863 let body_offsets: HashMap<NodeId, usize> = body_plan
864 .assignments
865 .iter()
866 .map(|(id, slot)| (*id, slot.offset))
867 .collect();
868 let mut body_d_output_off: Option<usize> = None;
869 let mut body_other_inputs: Vec<(NodeId, usize)> = Vec::new();
870 for n in body_vjp.nodes() {
871 if let Op::Input { name } = &n.op {
872 let off = body_offsets[&n.id];
873 if name == "d_output" {
874 body_d_output_off = Some(off);
875 } else {
876 body_other_inputs.push((n.id, off));
877 }
878 }
879 }
880 body_other_inputs.sort_by_key(|(id, _)| *id);
881 let body_d_output_off =
882 body_d_output_off.expect("ScanBackwardXs body_vjp missing 'd_output' Input");
883 let expected_others = 1 + *num_xs as usize;
884 assert_eq!(
885 body_other_inputs.len(),
886 expected_others,
887 "ScanBackwardXs body_vjp has {} non-d_output Inputs; expected {}",
888 body_other_inputs.len(),
889 expected_others
890 );
891 let body_carry_in_off = body_other_inputs[0].1;
892 let body_x_offs: Vec<usize> = body_other_inputs
893 .iter()
894 .skip(1)
895 .map(|(_, off)| *off)
896 .collect();
897 let body_dcarry_out_off = body_offsets[&body_vjp.outputs[0]];
898 let dxs_out_node = body_vjp.outputs[1 + *xs_idx as usize];
899 let body_dxs_out_off = body_offsets[&dxs_out_node];
900
901 let mut body_arena = crate::arena::Arena::from_plan(body_plan);
902 for n in body_vjp.nodes() {
903 if let Op::Constant { data } = &n.op
904 && body_arena.has_buffer(n.id)
905 && !data.is_empty()
906 {
907 match n.shape.dtype() {
908 rlx_ir::DType::F64 => {
909 let off = body_arena.byte_offset(n.id);
910 let buf = body_arena.raw_buf_mut();
911 let nb = (buf.len() - off).min(data.len());
912 buf[off..off + nb].copy_from_slice(&data[..nb]);
913 }
914 _ => {
915 let buf = body_arena.slice_mut(n.id);
916 let nf = data.len() / 4;
917 let nl = buf.len().min(nf);
918 for i in 0..nl {
919 let bytes = [
920 data[i * 4],
921 data[i * 4 + 1],
922 data[i * 4 + 2],
923 data[i * 4 + 3],
924 ];
925 buf[i] = f32::from_le_bytes(bytes);
926 }
927 }
928 }
929 }
930 }
931 let body_init = body_arena.raw_buf().to_vec();
932 let body_schedule = compile_thunks_with_rng(body_vjp, &body_arena, rng);
933
934 let carry_bytes = body_vjp
935 .node(body_vjp.outputs[0])
936 .shape
937 .size_bytes()
938 .expect("ScanBackwardXs dcarry must be statically shaped");
939 let carry_elem_size = body_vjp
940 .node(body_vjp.outputs[0])
941 .shape
942 .dtype()
943 .size_bytes() as u32;
944 let per_step_bytes = body_vjp
945 .node(dxs_out_node)
946 .shape
947 .size_bytes()
948 .expect("ScanBackwardXs dxs body output must be statically shaped");
949
950 let mut outer_xs_offs: Vec<(usize, u32)> = Vec::with_capacity(*num_xs as usize);
951 for i in 0..*num_xs as usize {
952 let outer_xs_id = node.inputs[3 + i];
953 let outer_xs_off = node_offset(arena, outer_xs_id);
954 let outer_xs_shape = &graph.node(outer_xs_id).shape;
955 let total = outer_xs_shape
956 .size_bytes()
957 .expect("ScanBackwardXs xs must have static shape");
958 let per_step = total / *length as usize;
959 outer_xs_offs.push((outer_xs_off, per_step as u32));
960 }
961
962 let (fb_schedule, fb_init, fb_carry_in_off, fb_output_off, fb_x_offs) = if is_recursive {
965 let fb = forward_body.as_ref().unwrap();
966 let fb_plan = rlx_opt::memory::plan_memory(fb);
967 let fb_offsets: HashMap<NodeId, usize> = fb_plan
968 .assignments
969 .iter()
970 .map(|(id, slot)| (*id, slot.offset))
971 .collect();
972 let mut fb_inputs: Vec<NodeId> = fb
973 .nodes()
974 .iter()
975 .filter(|n| matches!(n.op, Op::Input { .. }))
976 .map(|n| n.id)
977 .collect();
978 fb_inputs.sort();
979 let fb_carry = fb_offsets[&fb_inputs[0]];
980 let fb_xs: Vec<usize> = (1..fb_inputs.len())
981 .map(|i| fb_offsets[&fb_inputs[i]])
982 .collect();
983 let fb_out = fb_offsets[&fb.outputs[0]];
984 let mut fb_arena = crate::arena::Arena::from_plan(fb_plan);
985 for n in fb.nodes() {
986 if let Op::Constant { data } = &n.op
987 && fb_arena.has_buffer(n.id)
988 && !data.is_empty()
989 {
990 let off = fb_arena.byte_offset(n.id);
997 let buf = fb_arena.raw_buf_mut();
998 let nb = (buf.len() - off).min(data.len());
999 buf[off..off + nb].copy_from_slice(&data[..nb]);
1000 }
1001 }
1002 let fb_init_bytes = fb_arena.raw_buf().to_vec();
1003 let fb_sched = compile_thunks_with_rng(fb, &fb_arena, rng);
1004 (
1005 Some(Arc::new(fb_sched)),
1006 Some(Arc::new(fb_init_bytes)),
1007 fb_carry,
1008 fb_out,
1009 fb_xs,
1010 )
1011 } else {
1012 (None, None, 0, 0, Vec::new())
1013 };
1014
1015 Thunk::ScanBackwardXs {
1016 body_vjp: Arc::new(body_schedule),
1017 body_init: Arc::new(body_init),
1018 body_carry_in_off,
1019 body_x_offs: Arc::new(body_x_offs),
1020 body_d_output_off,
1021 body_dcarry_out_off,
1022 body_dxs_out_off,
1023 outer_init_off: node_offset(arena, node.inputs[0]),
1024 outer_traj_off: node_offset(arena, node.inputs[1]),
1025 outer_upstream_off: node_offset(arena, node.inputs[2]),
1026 outer_xs_offs: Arc::new(outer_xs_offs),
1027 outer_dxs_off: node_offset(arena, node.id),
1028 length: *length,
1029 carry_bytes: carry_bytes as u32,
1030 carry_elem_size,
1031 per_step_bytes: per_step_bytes as u32,
1032 save_trajectory: *save_trajectory,
1033 num_checkpoints: *num_checkpoints,
1034 forward_body: fb_schedule,
1035 forward_body_init: fb_init,
1036 forward_body_carry_in_off: fb_carry_in_off,
1037 forward_body_output_off: fb_output_off,
1038 forward_body_x_offs: Arc::new(fb_x_offs),
1039 }
1040 }
1041}
1042
1043#[inline(always)]
1044pub(crate) fn exec_scan(t: &Thunk, base: *mut u8) {
1045 let Thunk::Scan {
1046 body,
1047 body_init,
1048 body_input_off,
1049 body_output_off,
1050 outer_init_off,
1051 outer_final_off,
1052 length,
1053 carry_bytes,
1054 save_trajectory,
1055 xs_inputs,
1056 bcast_inputs,
1057 num_checkpoints,
1058 } = t
1059 else {
1060 unreachable!()
1061 };
1062 {
1063 let cb = *carry_bytes as usize;
1064 let n_steps = *length as usize;
1065 let k_total = if *num_checkpoints == 0 || *num_checkpoints == *length {
1069 n_steps } else {
1071 *num_checkpoints as usize
1072 };
1073 let checkpoint_t_for_k = |k: usize| -> usize {
1074 if k_total == n_steps {
1075 k
1076 } else {
1077 ((k + 1) * n_steps)
1078 .div_ceil(k_total)
1079 .saturating_sub(1)
1080 .min(n_steps - 1)
1081 }
1082 };
1083 let mut next_k = 0usize;
1084
1085 let mut body_buf: Vec<u8> = (**body_init).clone();
1086 unsafe {
1087 std::ptr::copy_nonoverlapping(
1088 base.add(*outer_init_off),
1089 body_buf.as_mut_ptr().add(*body_input_off),
1090 cb,
1091 );
1092 for (body_b_off, outer_b_off, total_bytes) in bcast_inputs.iter() {
1096 std::ptr::copy_nonoverlapping(
1097 base.add(*outer_b_off),
1098 body_buf.as_mut_ptr().add(*body_b_off),
1099 *total_bytes as usize,
1100 );
1101 }
1102 }
1103 for t in 0..n_steps {
1104 for (body_x_off, outer_xs_off, per_step_bytes) in xs_inputs.iter() {
1105 let psb = *per_step_bytes as usize;
1106 unsafe {
1107 std::ptr::copy_nonoverlapping(
1108 base.add(*outer_xs_off + t * psb),
1109 body_buf.as_mut_ptr().add(*body_x_off),
1110 psb,
1111 );
1112 }
1113 }
1114
1115 execute_thunks(body, &mut body_buf);
1116
1117 if *save_trajectory && next_k < k_total && t == checkpoint_t_for_k(next_k) {
1118 unsafe {
1119 std::ptr::copy_nonoverlapping(
1120 body_buf.as_ptr().add(*body_output_off),
1121 base.add(*outer_final_off + next_k * cb),
1122 cb,
1123 );
1124 }
1125 next_k += 1;
1126 }
1127
1128 if *body_output_off != *body_input_off {
1129 body_buf.copy_within(*body_output_off..*body_output_off + cb, *body_input_off);
1130 }
1131 }
1132
1133 if !*save_trajectory {
1134 unsafe {
1136 std::ptr::copy_nonoverlapping(
1137 body_buf.as_ptr().add(*body_output_off),
1138 base.add(*outer_final_off),
1139 cb,
1140 );
1141 }
1142 }
1143 }
1144}
1145
1146#[inline(always)]
1147pub(crate) fn exec_scan_backward_xs(t: &Thunk, base: *mut u8) {
1148 let Thunk::ScanBackwardXs {
1149 body_vjp,
1150 body_init,
1151 body_carry_in_off,
1152 body_x_offs,
1153 body_d_output_off,
1154 body_dcarry_out_off,
1155 body_dxs_out_off,
1156 outer_init_off,
1157 outer_traj_off,
1158 outer_upstream_off,
1159 outer_xs_offs,
1160 outer_dxs_off,
1161 length,
1162 carry_bytes,
1163 carry_elem_size,
1164 per_step_bytes,
1165 save_trajectory,
1166 num_checkpoints,
1167 forward_body,
1168 forward_body_init,
1169 forward_body_carry_in_off,
1170 forward_body_output_off,
1171 forward_body_x_offs,
1172 } = t
1173 else {
1174 unreachable!()
1175 };
1176 {
1177 let cb = *carry_bytes as usize;
1178 let psb = *per_step_bytes as usize;
1179 let n_steps = *length as usize;
1180 let k_total = *num_checkpoints as usize;
1181 let is_recursive = k_total != 0 && k_total != n_steps;
1182 let checkpoint_t_for_k = |k: usize| -> usize {
1183 ((k + 1) * n_steps)
1184 .div_ceil(k_total)
1185 .saturating_sub(1)
1186 .min(n_steps - 1)
1187 };
1188
1189 let mut fwd_buf: Vec<u8> = if is_recursive {
1193 (**forward_body_init.as_ref().unwrap()).clone()
1194 } else {
1195 Vec::new()
1196 };
1197 let mut seg_cache: Vec<u8> = Vec::new();
1198 let mut seg_start_t: usize = usize::MAX;
1199 let mut seg_count: usize = 0;
1200 let recompute_carry_t = |t: usize,
1201 dst: &mut [u8],
1202 fwd_buf: &mut Vec<u8>,
1203 seg_cache: &mut Vec<u8>,
1204 seg_start_t: &mut usize,
1205 seg_count: &mut usize| {
1206 if !is_recursive {
1207 unsafe {
1208 let src = if t == 0 {
1209 base.add(*outer_init_off)
1210 } else {
1211 base.add(*outer_traj_off + (t - 1) * cb)
1212 };
1213 std::ptr::copy_nonoverlapping(src, dst.as_mut_ptr(), cb);
1214 }
1215 return;
1216 }
1217 if *seg_start_t != usize::MAX && t >= *seg_start_t && t < *seg_start_t + *seg_count {
1218 let off = (t - *seg_start_t) * cb;
1219 dst.copy_from_slice(&seg_cache[off..off + cb]);
1220 return;
1221 }
1222 let seg_k = (0..k_total)
1223 .find(|&k| t <= checkpoint_t_for_k(k))
1224 .unwrap_or(k_total - 1);
1225 let (anchor_t, anchor_ptr): (usize, *const u8) = if seg_k == 0 {
1226 (0, unsafe { base.add(*outer_init_off) as *const u8 })
1227 } else {
1228 let prev_ck = checkpoint_t_for_k(seg_k - 1);
1229 (prev_ck + 1, unsafe {
1230 base.add(*outer_traj_off + (seg_k - 1) * cb) as *const u8
1231 })
1232 };
1233 let seg_end_t = checkpoint_t_for_k(seg_k);
1234 let seg_size = seg_end_t - anchor_t + 1;
1235
1236 fwd_buf.copy_from_slice(forward_body_init.as_ref().unwrap());
1237 unsafe {
1238 std::ptr::copy_nonoverlapping(
1239 anchor_ptr,
1240 fwd_buf.as_mut_ptr().add(*forward_body_carry_in_off),
1241 cb,
1242 );
1243 }
1244 seg_cache.resize(seg_size * cb, 0u8);
1245 seg_cache[0..cb].copy_from_slice(
1246 &fwd_buf[*forward_body_carry_in_off..*forward_body_carry_in_off + cb],
1247 );
1248 let fb_sched = forward_body.as_ref().unwrap();
1249 for i in 1..seg_size {
1250 let cur_iter = anchor_t + i - 1;
1251 for (idx, fb_x_off) in forward_body_x_offs.iter().enumerate() {
1252 let (outer_xs_off, x_psb) = outer_xs_offs[idx];
1253 let xb = x_psb as usize;
1254 unsafe {
1255 std::ptr::copy_nonoverlapping(
1256 base.add(outer_xs_off + cur_iter * xb),
1257 fwd_buf.as_mut_ptr().add(*fb_x_off),
1258 xb,
1259 );
1260 }
1261 }
1262 execute_thunks(fb_sched, fwd_buf);
1263 if *forward_body_output_off != *forward_body_carry_in_off {
1264 fwd_buf.copy_within(
1265 *forward_body_output_off..*forward_body_output_off + cb,
1266 *forward_body_carry_in_off,
1267 );
1268 }
1269 let cache_off = i * cb;
1270 seg_cache[cache_off..cache_off + cb].copy_from_slice(
1271 &fwd_buf[*forward_body_carry_in_off..*forward_body_carry_in_off + cb],
1272 );
1273 }
1274 *seg_start_t = anchor_t;
1275 *seg_count = seg_size;
1276
1277 let off = (t - anchor_t) * cb;
1278 dst.copy_from_slice(&seg_cache[off..off + cb]);
1279 };
1280
1281 let mut dcarry: Vec<u8> = vec![0u8; cb];
1282 if !*save_trajectory {
1283 unsafe {
1284 std::ptr::copy_nonoverlapping(
1285 base.add(*outer_upstream_off),
1286 dcarry.as_mut_ptr(),
1287 cb,
1288 );
1289 }
1290 }
1291
1292 let mut body_buf: Vec<u8> = (**body_init).clone();
1293
1294 for t in (0..n_steps).rev() {
1295 if *save_trajectory {
1296 unsafe {
1297 let up_off = *outer_upstream_off + t * cb;
1298 match *carry_elem_size {
1299 4 => {
1300 let up_ptr = base.add(up_off) as *const f32;
1301 let dc_ptr = dcarry.as_mut_ptr() as *mut f32;
1302 let n_elems = cb / 4;
1303 for i in 0..n_elems {
1304 *dc_ptr.add(i) += *up_ptr.add(i);
1305 }
1306 }
1307 8 => {
1308 let up_ptr = base.add(up_off) as *const f64;
1309 let dc_ptr = dcarry.as_mut_ptr() as *mut f64;
1310 let n_elems = cb / 8;
1311 for i in 0..n_elems {
1312 *dc_ptr.add(i) += *up_ptr.add(i);
1313 }
1314 }
1315 other => panic!(
1316 "ScanBackwardXs: unsupported carry elem size {other} \
1317 (only f32/f64 carries are supported today)"
1318 ),
1319 }
1320 }
1321 }
1322
1323 let carry_dst_start = *body_carry_in_off;
1327 {
1328 let carry_slice = &mut body_buf[carry_dst_start..carry_dst_start + cb];
1329 recompute_carry_t(
1330 t,
1331 carry_slice,
1332 &mut fwd_buf,
1333 &mut seg_cache,
1334 &mut seg_start_t,
1335 &mut seg_count,
1336 );
1337 }
1338 unsafe {
1339 for (i, body_x_off) in body_x_offs.iter().enumerate() {
1340 let (outer_xs_off, x_psb) = outer_xs_offs[i];
1341 let xb = x_psb as usize;
1342 std::ptr::copy_nonoverlapping(
1343 base.add(outer_xs_off + t * xb),
1344 body_buf.as_mut_ptr().add(*body_x_off),
1345 xb,
1346 );
1347 }
1348 std::ptr::copy_nonoverlapping(
1349 dcarry.as_ptr(),
1350 body_buf.as_mut_ptr().add(*body_d_output_off),
1351 cb,
1352 );
1353 }
1354
1355 execute_thunks(body_vjp, &mut body_buf);
1356
1357 unsafe {
1360 std::ptr::copy_nonoverlapping(
1361 body_buf.as_ptr().add(*body_dxs_out_off),
1362 base.add(*outer_dxs_off + t * psb),
1363 psb,
1364 );
1365 }
1366
1367 unsafe {
1369 std::ptr::copy_nonoverlapping(
1370 body_buf.as_ptr().add(*body_dcarry_out_off),
1371 dcarry.as_mut_ptr(),
1372 cb,
1373 );
1374 }
1375 }
1376 }
1377}
1378
1379#[inline(always)]
1380pub(crate) fn exec_gated_delta_net(t: &Thunk, base: *mut u8) {
1381 let Thunk::GatedDeltaNet {
1382 q,
1383 k,
1384 v,
1385 g,
1386 beta,
1387 state,
1388 dst,
1389 batch,
1390 seq,
1391 heads,
1392 state_size,
1393 gate_per_channel,
1394 carry_state,
1395 } = t
1396 else {
1397 unreachable!()
1398 };
1399 unsafe {
1400 execute_gated_delta_net_f32(
1401 *q,
1402 *k,
1403 *v,
1404 *g,
1405 *beta,
1406 *state,
1407 *dst,
1408 *batch as usize,
1409 *seq as usize,
1410 *heads as usize,
1411 *state_size as usize,
1412 *gate_per_channel,
1413 *carry_state,
1414 base,
1415 );
1416 }
1417}
1418
1419#[inline(always)]
1420pub(crate) fn exec_lstm(t: &Thunk, base: *mut u8) {
1421 let Thunk::Lstm {
1422 x,
1423 w_ih,
1424 w_hh,
1425 bias,
1426 h0,
1427 c0,
1428 dst,
1429 batch,
1430 seq,
1431 input_size,
1432 hidden,
1433 num_layers,
1434 bidirectional,
1435 carry,
1436 } = t
1437 else {
1438 unreachable!()
1439 };
1440 unsafe {
1441 execute_lstm_f32(
1442 *x,
1443 *w_ih,
1444 *w_hh,
1445 *bias,
1446 *h0,
1447 *c0,
1448 *dst,
1449 *batch as usize,
1450 *seq as usize,
1451 *input_size as usize,
1452 *hidden as usize,
1453 *num_layers as usize,
1454 *bidirectional,
1455 *carry,
1456 base,
1457 );
1458 }
1459}
1460
1461#[inline(always)]
1462pub(crate) fn exec_gru(t: &Thunk, base: *mut u8) {
1463 let Thunk::Gru {
1464 x,
1465 w_ih,
1466 w_hh,
1467 b_ih,
1468 b_hh,
1469 h0,
1470 dst,
1471 batch,
1472 seq,
1473 input_size,
1474 hidden,
1475 num_layers,
1476 bidirectional,
1477 carry,
1478 } = t
1479 else {
1480 unreachable!()
1481 };
1482 unsafe {
1483 execute_gru_f32(
1484 *x,
1485 *w_ih,
1486 *w_hh,
1487 *b_ih,
1488 *b_hh,
1489 *h0,
1490 *dst,
1491 *batch as usize,
1492 *seq as usize,
1493 *input_size as usize,
1494 *hidden as usize,
1495 *num_layers as usize,
1496 *bidirectional,
1497 *carry,
1498 base,
1499 );
1500 }
1501}
1502
1503#[inline(always)]
1504pub(crate) fn exec_rnn(t: &Thunk, base: *mut u8) {
1505 let Thunk::Rnn {
1506 x,
1507 w_ih,
1508 w_hh,
1509 bias,
1510 h0,
1511 dst,
1512 batch,
1513 seq,
1514 input_size,
1515 hidden,
1516 num_layers,
1517 bidirectional,
1518 carry,
1519 relu,
1520 } = t
1521 else {
1522 unreachable!()
1523 };
1524 unsafe {
1525 execute_rnn_f32(
1526 *x,
1527 *w_ih,
1528 *w_hh,
1529 *bias,
1530 *h0,
1531 *dst,
1532 *batch as usize,
1533 *seq as usize,
1534 *input_size as usize,
1535 *hidden as usize,
1536 *num_layers as usize,
1537 *bidirectional,
1538 *carry,
1539 *relu,
1540 base,
1541 );
1542 }
1543}
1544
1545#[inline(always)]
1546pub(crate) fn exec_mamba2(t: &Thunk, base: *mut u8) {
1547 let Thunk::Mamba2 {
1548 x,
1549 dt,
1550 a,
1551 b,
1552 c,
1553 dst,
1554 batch,
1555 seq,
1556 heads,
1557 head_dim,
1558 state_size,
1559 } = t
1560 else {
1561 unreachable!()
1562 };
1563 unsafe {
1564 execute_mamba2_f32(
1565 *x,
1566 *dt,
1567 *a,
1568 *b,
1569 *c,
1570 *dst,
1571 *batch as usize,
1572 *seq as usize,
1573 *heads as usize,
1574 *head_dim as usize,
1575 *state_size as usize,
1576 base,
1577 );
1578 }
1579}
1580
1581#[inline(always)]
1582pub(crate) fn exec_selective_scan(t: &Thunk, base: *mut u8) {
1583 let Thunk::SelectiveScan {
1584 x,
1585 delta,
1586 a,
1587 b: bp,
1588 c: cp,
1589 dst,
1590 batch,
1591 seq,
1592 hidden,
1593 state_size,
1594 } = t
1595 else {
1596 unreachable!()
1597 };
1598 unsafe {
1599 execute_selective_scan_f32(
1600 *x,
1601 *delta,
1602 *a,
1603 *bp,
1604 *cp,
1605 *dst,
1606 *batch as usize,
1607 *seq as usize,
1608 *hidden as usize,
1609 *state_size as usize,
1610 base,
1611 );
1612 }
1613}
1614
1615#[allow(clippy::too_many_arguments)]
1630pub(crate) unsafe fn griewank_process_segment(
1631 t_lo: usize,
1632 t_hi: usize,
1633 anchor_carry: &[u8],
1634 cb: usize,
1635 fwd_sched: &ThunkSchedule,
1636 fwd_init: &[u8],
1637 fwd_carry_in_off: usize,
1638 fwd_output_off: usize,
1639 fwd_x_offs: &[usize],
1640 base: *mut u8,
1641 outer_xs_offs: &[(usize, u32)],
1642 fwd_buf: &mut Vec<u8>,
1643 leaf_threshold: usize,
1644 process_iter: &mut dyn FnMut(usize, &[u8]),
1645) {
1646 unsafe {
1647 let size = t_hi - t_lo + 1;
1648 if size == 1 {
1649 process_iter(t_lo, anchor_carry);
1650 return;
1651 }
1652 if size <= leaf_threshold {
1653 let mut cache: Vec<u8> = Vec::with_capacity(size * cb);
1655 cache.extend_from_slice(anchor_carry);
1656 fwd_buf.copy_from_slice(fwd_init);
1657 std::ptr::copy_nonoverlapping(
1658 anchor_carry.as_ptr(),
1659 fwd_buf.as_mut_ptr().add(fwd_carry_in_off),
1660 cb,
1661 );
1662 for i in 1..size {
1663 let cur_iter = t_lo + i - 1;
1664 for (idx, fb_x_off) in fwd_x_offs.iter().enumerate() {
1665 let (outer_xs_off, x_psb) = outer_xs_offs[idx];
1666 let xb = x_psb as usize;
1667 std::ptr::copy_nonoverlapping(
1668 base.add(outer_xs_off + cur_iter * xb),
1669 fwd_buf.as_mut_ptr().add(*fb_x_off),
1670 xb,
1671 );
1672 }
1673 execute_thunks(fwd_sched, fwd_buf);
1674 if fwd_output_off != fwd_carry_in_off {
1675 fwd_buf.copy_within(fwd_output_off..fwd_output_off + cb, fwd_carry_in_off);
1676 }
1677 cache.extend_from_slice(&fwd_buf[fwd_carry_in_off..fwd_carry_in_off + cb]);
1678 }
1679 for t in (t_lo..=t_hi).rev() {
1681 let idx = t - t_lo;
1682 let carry = &cache[idx * cb..(idx + 1) * cb];
1683 process_iter(t, carry);
1684 }
1685 return;
1686 }
1687
1688 let mid = t_lo + size / 2;
1692 fwd_buf.copy_from_slice(fwd_init);
1693 std::ptr::copy_nonoverlapping(
1694 anchor_carry.as_ptr(),
1695 fwd_buf.as_mut_ptr().add(fwd_carry_in_off),
1696 cb,
1697 );
1698 for cur_iter in t_lo..mid {
1699 for (idx, fb_x_off) in fwd_x_offs.iter().enumerate() {
1700 let (outer_xs_off, x_psb) = outer_xs_offs[idx];
1701 let xb = x_psb as usize;
1702 std::ptr::copy_nonoverlapping(
1703 base.add(outer_xs_off + cur_iter * xb),
1704 fwd_buf.as_mut_ptr().add(*fb_x_off),
1705 xb,
1706 );
1707 }
1708 execute_thunks(fwd_sched, fwd_buf);
1709 if fwd_output_off != fwd_carry_in_off {
1710 fwd_buf.copy_within(fwd_output_off..fwd_output_off + cb, fwd_carry_in_off);
1711 }
1712 }
1713 let mid_carry: Vec<u8> = fwd_buf[fwd_carry_in_off..fwd_carry_in_off + cb].to_vec();
1714
1715 griewank_process_segment(
1719 mid,
1720 t_hi,
1721 &mid_carry,
1722 cb,
1723 fwd_sched,
1724 fwd_init,
1725 fwd_carry_in_off,
1726 fwd_output_off,
1727 fwd_x_offs,
1728 base,
1729 outer_xs_offs,
1730 fwd_buf,
1731 leaf_threshold,
1732 process_iter,
1733 );
1734 griewank_process_segment(
1736 t_lo,
1737 mid - 1,
1738 anchor_carry,
1739 cb,
1740 fwd_sched,
1741 fwd_init,
1742 fwd_carry_in_off,
1743 fwd_output_off,
1744 fwd_x_offs,
1745 base,
1746 outer_xs_offs,
1747 fwd_buf,
1748 leaf_threshold,
1749 process_iter,
1750 );
1751 }
1752}
1753
1754#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1763pub struct RnnLens {
1764 pub x: usize,
1766 pub w_ih: usize,
1768 pub w_hh: usize,
1770 pub bias: usize,
1772 pub state: usize,
1774 pub out: usize,
1776}
1777
1778pub fn rnn_expected_lens(
1780 gates: usize,
1781 batch: usize,
1782 seq: usize,
1783 input_size: usize,
1784 hidden: usize,
1785 num_layers: usize,
1786 bidirectional: bool,
1787) -> RnnLens {
1788 let dirs = if bidirectional { 2 } else { 1 };
1789 let w_ih: usize = (0..num_layers)
1790 .map(|l| dirs * gates * hidden * if l == 0 { input_size } else { dirs * hidden })
1791 .sum();
1792 RnnLens {
1793 x: batch * seq * input_size,
1794 w_ih,
1795 w_hh: num_layers * dirs * gates * hidden * hidden,
1796 bias: num_layers * dirs * gates * hidden,
1797 state: num_layers * dirs * batch * hidden,
1798 out: batch * seq * dirs * hidden,
1799 }
1800}
1801
1802pub fn check_rnn_lens(op: &str, checks: &[(&str, usize, usize)]) -> Result<(), String> {
1805 for &(name, actual, expected) in checks {
1806 if actual != expected {
1807 return Err(format!(
1808 "{op}: input '{name}' has {actual} elements, expected {expected} \
1809 (mismatch would read out of bounds in the f32 kernel)"
1810 ));
1811 }
1812 }
1813 Ok(())
1814}
1815
1816#[allow(clippy::too_many_arguments)]
1824pub unsafe fn execute_lstm_f32(
1825 x: usize,
1826 w_ih: usize,
1827 w_hh: usize,
1828 bias: usize,
1829 h0: usize,
1830 c0: usize,
1831 dst: usize,
1832 batch: usize,
1833 seq: usize,
1834 input_size: usize,
1835 hidden: usize,
1836 num_layers: usize,
1837 bidirectional: bool,
1838 carry: bool,
1839 base: *mut u8,
1840) {
1841 #[inline]
1842 fn sigmoid(z: f32) -> f32 {
1843 1.0 / (1.0 + (-z).exp())
1844 }
1845
1846 let bptr = base as usize;
1847 let four_h = 4 * hidden;
1848 let dirs = if bidirectional { 2 } else { 1 };
1849
1850 unsafe {
1851 let f32s = |off: usize, n: usize| -> &[f32] {
1852 std::slice::from_raw_parts((bptr + off) as *const f32, n)
1853 };
1854
1855 let mut layer_in: Vec<f32> = f32s(x, batch * seq * input_size).to_vec();
1857 let mut in_l = input_size;
1858 let mut wih_cursor = 0usize;
1861
1862 for l in 0..num_layers {
1863 let out_width = dirs * hidden;
1864 let mut layer_out = vec![0f32; batch * seq * out_width];
1865 let lo_ptr = layer_out.as_mut_ptr() as usize;
1866 let li_ref: &[f32] = &layer_in;
1867 let wih_block = four_h * in_l;
1868
1869 for dir in 0..dirs {
1870 let ld = l * dirs + dir;
1871 let wih = f32s((w_ih / 4 + wih_cursor + dir * wih_block) * 4, wih_block);
1872 let whh = f32s(w_hh + ld * four_h * hidden * 4, four_h * hidden);
1873 let bs = f32s(bias + ld * four_h * 4, four_h);
1874 let h0p = bptr + h0 + ld * batch * hidden * 4;
1875 let c0p = bptr + c0 + ld * batch * hidden * 4;
1876
1877 crate::pool::par_range(batch, |b| {
1878 let lo = lo_ptr as *mut f32;
1879 let mut h = vec![0f32; hidden];
1880 let mut c = vec![0f32; hidden];
1881 if carry {
1882 let hin = std::slice::from_raw_parts(
1883 (h0p + b * hidden * 4) as *const f32,
1884 hidden,
1885 );
1886 let cin = std::slice::from_raw_parts(
1887 (c0p + b * hidden * 4) as *const f32,
1888 hidden,
1889 );
1890 h.copy_from_slice(hin);
1891 c.copy_from_slice(cin);
1892 }
1893 let mut z = vec![0f32; four_h];
1894 for step in 0..seq {
1895 let t = if dir == 0 { step } else { seq - 1 - step };
1896 let x_t = &li_ref[(b * seq + t) * in_l..(b * seq + t + 1) * in_l];
1897 for r in 0..four_h {
1898 let wr = &wih[r * in_l..(r + 1) * in_l];
1899 let mut acc = bs[r];
1900 for j in 0..in_l {
1901 acc += wr[j] * x_t[j];
1902 }
1903 let hr = &whh[r * hidden..(r + 1) * hidden];
1904 for (j, &hj) in h.iter().enumerate() {
1905 acc += hr[j] * hj;
1906 }
1907 z[r] = acc;
1908 }
1909 for k in 0..hidden {
1910 let i_g = sigmoid(z[k]);
1911 let f_g = sigmoid(z[hidden + k]);
1912 let g_g = z[2 * hidden + k].tanh();
1913 let o_g = sigmoid(z[3 * hidden + k]);
1914 let c_new = f_g * c[k] + i_g * g_g;
1915 c[k] = c_new;
1916 let h_new = o_g * c_new.tanh();
1917 h[k] = h_new;
1918 *lo.add((b * seq + t) * out_width + dir * hidden + k) = h_new;
1921 }
1922 }
1923 if carry {
1924 let hout = std::slice::from_raw_parts_mut(
1925 (h0p + b * hidden * 4) as *mut f32,
1926 hidden,
1927 );
1928 let cout = std::slice::from_raw_parts_mut(
1929 (c0p + b * hidden * 4) as *mut f32,
1930 hidden,
1931 );
1932 hout.copy_from_slice(&h);
1933 cout.copy_from_slice(&c);
1934 }
1935 });
1936 }
1937
1938 wih_cursor += dirs * wih_block;
1939 layer_in = layer_out;
1940 in_l = out_width;
1941 }
1942
1943 let dst_slice = std::slice::from_raw_parts_mut((bptr + dst) as *mut f32, layer_in.len());
1945 dst_slice.copy_from_slice(&layer_in);
1946 }
1947}
1948
1949#[allow(clippy::too_many_arguments)]
1955pub unsafe fn execute_gru_f32(
1956 x: usize,
1957 w_ih: usize,
1958 w_hh: usize,
1959 b_ih: usize,
1960 b_hh: usize,
1961 h0: usize,
1962 dst: usize,
1963 batch: usize,
1964 seq: usize,
1965 input_size: usize,
1966 hidden: usize,
1967 num_layers: usize,
1968 bidirectional: bool,
1969 carry: bool,
1970 base: *mut u8,
1971) {
1972 #[inline]
1973 fn sigmoid(z: f32) -> f32 {
1974 1.0 / (1.0 + (-z).exp())
1975 }
1976
1977 let bptr = base as usize;
1978 let three_h = 3 * hidden;
1979 let dirs = if bidirectional { 2 } else { 1 };
1980
1981 unsafe {
1982 let f32s = |off: usize, n: usize| -> &[f32] {
1983 std::slice::from_raw_parts((bptr + off) as *const f32, n)
1984 };
1985
1986 let mut layer_in: Vec<f32> = f32s(x, batch * seq * input_size).to_vec();
1987 let mut in_l = input_size;
1988 let mut wih_cursor = 0usize;
1989
1990 for l in 0..num_layers {
1991 let out_width = dirs * hidden;
1992 let mut layer_out = vec![0f32; batch * seq * out_width];
1993 let lo_ptr = layer_out.as_mut_ptr() as usize;
1994 let li_ref: &[f32] = &layer_in;
1995 let wih_block = three_h * in_l;
1996
1997 for dir in 0..dirs {
1998 let ld = l * dirs + dir;
1999 let wih = f32s((w_ih / 4 + wih_cursor + dir * wih_block) * 4, wih_block);
2000 let whh = f32s(w_hh + ld * three_h * hidden * 4, three_h * hidden);
2001 let bih = f32s(b_ih + ld * three_h * 4, three_h);
2002 let bhh = f32s(b_hh + ld * three_h * 4, three_h);
2003 let h0p = bptr + h0 + ld * batch * hidden * 4;
2004
2005 crate::pool::par_range(batch, |b| {
2006 let lo = lo_ptr as *mut f32;
2007 let mut h = vec![0f32; hidden];
2008 if carry {
2009 let hin = std::slice::from_raw_parts(
2010 (h0p + b * hidden * 4) as *const f32,
2011 hidden,
2012 );
2013 h.copy_from_slice(hin);
2014 }
2015 let mut xi = vec![0f32; three_h]; let mut hi = vec![0f32; three_h]; for step in 0..seq {
2018 let t = if dir == 0 { step } else { seq - 1 - step };
2019 let x_t = &li_ref[(b * seq + t) * in_l..(b * seq + t + 1) * in_l];
2020 for r in 0..three_h {
2021 let wr = &wih[r * in_l..(r + 1) * in_l];
2022 let mut a = bih[r];
2023 for j in 0..in_l {
2024 a += wr[j] * x_t[j];
2025 }
2026 xi[r] = a;
2027 let hr = &whh[r * hidden..(r + 1) * hidden];
2028 let mut bb = bhh[r];
2029 for (j, &hj) in h.iter().enumerate() {
2030 bb += hr[j] * hj;
2031 }
2032 hi[r] = bb;
2033 }
2034 for k in 0..hidden {
2035 let rg = sigmoid(xi[k] + hi[k]);
2036 let zg = sigmoid(xi[hidden + k] + hi[hidden + k]);
2037 let ng = (xi[2 * hidden + k] + rg * hi[2 * hidden + k]).tanh();
2039 let h_new = (1.0 - zg) * ng + zg * h[k];
2040 h[k] = h_new;
2041 *lo.add((b * seq + t) * out_width + dir * hidden + k) = h_new;
2042 }
2043 }
2044 if carry {
2045 let hout = std::slice::from_raw_parts_mut(
2046 (h0p + b * hidden * 4) as *mut f32,
2047 hidden,
2048 );
2049 hout.copy_from_slice(&h);
2050 }
2051 });
2052 }
2053
2054 wih_cursor += dirs * wih_block;
2055 layer_in = layer_out;
2056 in_l = out_width;
2057 }
2058
2059 let dst_slice = std::slice::from_raw_parts_mut((bptr + dst) as *mut f32, layer_in.len());
2060 dst_slice.copy_from_slice(&layer_in);
2061 }
2062}
2063
2064#[allow(clippy::too_many_arguments)]
2069pub unsafe fn execute_rnn_f32(
2070 x: usize,
2071 w_ih: usize,
2072 w_hh: usize,
2073 bias: usize,
2074 h0: usize,
2075 dst: usize,
2076 batch: usize,
2077 seq: usize,
2078 input_size: usize,
2079 hidden: usize,
2080 num_layers: usize,
2081 bidirectional: bool,
2082 carry: bool,
2083 relu: bool,
2084 base: *mut u8,
2085) {
2086 let bptr = base as usize;
2087 let dirs = if bidirectional { 2 } else { 1 };
2088
2089 unsafe {
2090 let f32s = |off: usize, n: usize| -> &[f32] {
2091 std::slice::from_raw_parts((bptr + off) as *const f32, n)
2092 };
2093
2094 let mut layer_in: Vec<f32> = f32s(x, batch * seq * input_size).to_vec();
2095 let mut in_l = input_size;
2096 let mut wih_cursor = 0usize;
2097
2098 for l in 0..num_layers {
2099 let out_width = dirs * hidden;
2100 let mut layer_out = vec![0f32; batch * seq * out_width];
2101 let lo_ptr = layer_out.as_mut_ptr() as usize;
2102 let li_ref: &[f32] = &layer_in;
2103 let wih_block = hidden * in_l;
2104
2105 for dir in 0..dirs {
2106 let ld = l * dirs + dir;
2107 let wih = f32s((w_ih / 4 + wih_cursor + dir * wih_block) * 4, wih_block);
2108 let whh = f32s(w_hh + ld * hidden * hidden * 4, hidden * hidden);
2109 let bs = f32s(bias + ld * hidden * 4, hidden);
2110 let h0p = bptr + h0 + ld * batch * hidden * 4;
2111
2112 crate::pool::par_range(batch, |b| {
2113 let lo = lo_ptr as *mut f32;
2114 let mut h = vec![0f32; hidden];
2115 if carry {
2116 let hin = std::slice::from_raw_parts(
2117 (h0p + b * hidden * 4) as *const f32,
2118 hidden,
2119 );
2120 h.copy_from_slice(hin);
2121 }
2122 for step in 0..seq {
2123 let t = if dir == 0 { step } else { seq - 1 - step };
2124 let x_t = &li_ref[(b * seq + t) * in_l..(b * seq + t + 1) * in_l];
2125 let mut h_new = vec![0f32; hidden];
2126 for k in 0..hidden {
2127 let wr = &wih[k * in_l..(k + 1) * in_l];
2128 let mut acc = bs[k];
2129 for j in 0..in_l {
2130 acc += wr[j] * x_t[j];
2131 }
2132 let hr = &whh[k * hidden..(k + 1) * hidden];
2133 for (j, &hj) in h.iter().enumerate() {
2134 acc += hr[j] * hj;
2135 }
2136 h_new[k] = if relu { acc.max(0.0) } else { acc.tanh() };
2137 }
2138 for k in 0..hidden {
2139 h[k] = h_new[k];
2140 *lo.add((b * seq + t) * out_width + dir * hidden + k) = h_new[k];
2141 }
2142 }
2143 if carry {
2144 let hout = std::slice::from_raw_parts_mut(
2145 (h0p + b * hidden * 4) as *mut f32,
2146 hidden,
2147 );
2148 hout.copy_from_slice(&h);
2149 }
2150 });
2151 }
2152
2153 wih_cursor += dirs * wih_block;
2154 layer_in = layer_out;
2155 in_l = out_width;
2156 }
2157
2158 let dst_slice = std::slice::from_raw_parts_mut((bptr + dst) as *mut f32, layer_in.len());
2159 dst_slice.copy_from_slice(&layer_in);
2160 }
2161}
2162
2163#[allow(clippy::too_many_arguments)]
2168pub unsafe fn execute_mamba2_f32(
2169 x: usize,
2170 dt: usize,
2171 a: usize,
2172 b: usize,
2173 c: usize,
2174 dst: usize,
2175 batch: usize,
2176 seq: usize,
2177 heads: usize,
2178 head_dim: usize,
2179 state_size: usize,
2180 base: *mut u8,
2181) {
2182 let (bn, s, h, p, n) = (batch, seq, heads, head_dim, state_size);
2183 let bptr = base as usize;
2184 unsafe {
2185 let f32s = |off: usize, len: usize| -> &[f32] {
2186 std::slice::from_raw_parts((bptr + off) as *const f32, len)
2187 };
2188 let xs = f32s(x, bn * s * h * p);
2189 let dts = f32s(dt, bn * s * h);
2190 let am = f32s(a, h);
2191 let bm = f32s(b, bn * s * h * n);
2192 let cm = f32s(c, bn * s * h * n);
2193 let out_ptr = bptr + dst;
2194
2195 crate::pool::par_range(bn * h, |bh| {
2197 let bi = bh / h;
2198 let hi = bh % h;
2199 let out = out_ptr as *mut f32;
2200 let mut state = vec![0f32; p * n];
2201 for t in 0..s {
2202 let dt_t = dts[(bi * s + t) * h + hi];
2203 let da = (dt_t * am[hi]).exp();
2204 let x_off = ((bi * s + t) * h + hi) * p;
2205 let bc_off = ((bi * s + t) * h + hi) * n;
2206 for pi in 0..p {
2207 let dtx = dt_t * xs[x_off + pi];
2208 for ni in 0..n {
2209 state[pi * n + ni] = da * state[pi * n + ni] + dtx * bm[bc_off + ni];
2210 }
2211 }
2212 for pi in 0..p {
2213 let mut acc = 0f32;
2214 for ni in 0..n {
2215 acc += state[pi * n + ni] * cm[bc_off + ni];
2216 }
2217 *out.add(x_off + pi) = acc;
2218 }
2219 }
2220 });
2221 }
2222}
2223
2224pub unsafe fn execute_selective_scan_f32(
2230 x: usize,
2231 delta: usize,
2232 a: usize,
2233 b: usize,
2234 c: usize,
2235 dst: usize,
2236 batch: usize,
2237 seq: usize,
2238 hidden: usize,
2239 state_size: usize,
2240 base: *mut u8,
2241) {
2242 let (bn, s, h, n) = (batch, seq, hidden, state_size);
2243 unsafe {
2244 let xs = sl(x, base, bn * s * h);
2245 let dt = sl(delta, base, bn * s * h);
2246 let am = sl(a, base, h * n);
2247 let bm = sl(b, base, bn * s * n);
2248 let cm = sl(c, base, bn * s * n);
2249 let out = sl_mut(dst, base, bn * s * h);
2250
2251 let mut state = vec![0f32; h * n];
2254 for bi in 0..bn {
2255 for v in state.iter_mut() {
2256 *v = 0.0;
2257 }
2258 for si in 0..s {
2259 let x_row = &xs[bi * s * h + si * h..bi * s * h + (si + 1) * h];
2260 let dt_row = &dt[bi * s * h + si * h..bi * s * h + (si + 1) * h];
2261 let b_row = &bm[bi * s * n + si * n..bi * s * n + (si + 1) * n];
2262 let c_row = &cm[bi * s * n + si * n..bi * s * n + (si + 1) * n];
2263 let out_row = &mut out[bi * s * h + si * h..bi * s * h + (si + 1) * h];
2264
2265 for ci in 0..h {
2266 let d = dt_row[ci];
2267 let xv = x_row[ci];
2268 let mut acc = 0f32;
2269 for ni in 0..n {
2270 let da = (d * am[ci * n + ni]).exp();
2272 state[ci * n + ni] = da * state[ci * n + ni] + d * b_row[ni] * xv;
2273 acc += c_row[ni] * state[ci * n + ni];
2274 }
2275 out_row[ci] = acc;
2276 }
2277 }
2278 }
2279 }
2280}
2281
2282#[allow(clippy::too_many_arguments)]
2283pub unsafe fn execute_gated_delta_net_f32(
2284 q: usize,
2285 k: usize,
2286 v: usize,
2287 g: usize,
2288 beta: usize,
2289 state: usize,
2290 dst: usize,
2291 batch: usize,
2292 seq: usize,
2293 heads: usize,
2294 state_size: usize,
2295 gate_per_channel: bool,
2296 carry_state: bool,
2297 base: *mut u8,
2298) {
2299 #[derive(Copy, Clone)]
2300 struct ArenaPtr(usize);
2301 unsafe impl Send for ArenaPtr {}
2302 unsafe impl Sync for ArenaPtr {}
2303 impl ArenaPtr {
2304 #[inline]
2305 fn get(self) -> *mut u8 {
2306 self.0 as *mut u8
2307 }
2308 }
2309
2310 unsafe {
2311 let arena = ArenaPtr(base as usize);
2312 let (b, s, h, n) = (batch, seq, heads, state_size);
2313 let scale = 1.0f32 / (n as f32).sqrt();
2314 let use_external = carry_state;
2315 let mut owned_state = vec![0f32; h * n * n];
2316
2317 crate::pool::num_threads();
2318
2319 assert!(
2320 n <= crate::gdn::GDN_MAX_STATE,
2321 "GatedDeltaNet state_size={n} exceeds stack scratch ({})",
2322 crate::gdn::GDN_MAX_STATE
2323 );
2324
2325 let qs = sl(q, arena.get(), b * s * h * n);
2326 let ks = sl(k, arena.get(), b * s * h * n);
2327 let vs = sl(v, arena.get(), b * s * h * n);
2328 let gs = sl(
2330 g,
2331 arena.get(),
2332 if gate_per_channel {
2333 b * s * h * n
2334 } else {
2335 b * s * h
2336 },
2337 );
2338 let betas = sl(beta, arena.get(), b * s * h);
2339 let _out = sl_mut(dst, arena.get(), b * s * h * n);
2340 let hs_n = h * n;
2341
2342 let run_head = |bi: usize, hi: usize, s_mat: &mut [f32], sk: &mut [f32]| {
2343 for ti in 0..s {
2344 let qkv_step = bi * s * hs_n + ti * hs_n + hi * n;
2345 let gb_step = bi * s * h + ti * h + hi;
2346 let out_row = sl_mut(dst + qkv_step * std::mem::size_of::<f32>(), arena.get(), n);
2347 let (q_r, k_r, v_r) = (
2348 &qs[qkv_step..qkv_step + n],
2349 &ks[qkv_step..qkv_step + n],
2350 &vs[qkv_step..qkv_step + n],
2351 );
2352 if gate_per_channel {
2353 crate::gdn::gdn_step_blas_pc(
2354 s_mat,
2355 q_r,
2356 k_r,
2357 v_r,
2358 &gs[gb_step * n..gb_step * n + n],
2359 betas[gb_step],
2360 out_row,
2361 sk,
2362 n,
2363 scale,
2364 );
2365 } else {
2366 crate::gdn::gdn_step_blas(
2367 s_mat,
2368 q_r,
2369 k_r,
2370 v_r,
2371 gs[gb_step],
2372 betas[gb_step],
2373 out_row,
2374 sk,
2375 n,
2376 scale,
2377 );
2378 }
2379 }
2380 };
2381
2382 if !use_external && s > 1 {
2385 for bi in 0..b {
2386 crate::pool::par_range(h, |hi| {
2387 let mut sk_buf = [0f32; crate::gdn::GDN_MAX_STATE];
2388 let sk = &mut sk_buf[..n];
2389 let mut local_state =
2390 [0f32; crate::gdn::GDN_MAX_STATE * crate::gdn::GDN_MAX_STATE];
2391 let s_mat = &mut local_state[..n * n];
2392 s_mat.fill(0.0);
2393 run_head(bi, hi, s_mat, sk);
2394 });
2395 }
2396 return;
2397 }
2398
2399 if use_external {
2400 let state_bytes = state;
2401 crate::pool::par_range(b * h, |bhi| {
2402 let bi = bhi / h;
2403 let hi = bhi % h;
2404 let elem_off = bi * h * n * n + hi * n * n;
2405 let s_mat = sl_mut(
2406 state_bytes + elem_off * std::mem::size_of::<f32>(),
2407 arena.get(),
2408 n * n,
2409 );
2410 let mut sk_buf = [0f32; crate::gdn::GDN_MAX_STATE];
2411 run_head(bi, hi, s_mat, &mut sk_buf[..n]);
2412 });
2413 } else {
2414 for bi in 0..b {
2415 owned_state.fill(0.0);
2416 #[cfg(not(target_arch = "wasm32"))]
2417 {
2418 use rayon::prelude::*;
2419 owned_state
2420 .par_chunks_mut(n * n)
2421 .enumerate()
2422 .for_each(|(hi, s_mat)| {
2423 let mut sk_buf = [0f32; crate::gdn::GDN_MAX_STATE];
2424 run_head(bi, hi, s_mat, &mut sk_buf[..n]);
2425 });
2426 }
2427 #[cfg(target_arch = "wasm32")]
2428 {
2429 owned_state
2430 .chunks_mut(n * n)
2431 .enumerate()
2432 .for_each(|(hi, s_mat)| {
2433 let mut sk_buf = [0f32; crate::gdn::GDN_MAX_STATE];
2434 run_head(bi, hi, s_mat, &mut sk_buf[..n]);
2435 });
2436 }
2437 }
2438 }
2439 }
2440}
2441
2442pub unsafe fn execute_gated_delta_net_f16(
2444 q: usize,
2445 k: usize,
2446 v: usize,
2447 g: usize,
2448 beta: usize,
2449 state: usize,
2450 dst: usize,
2451 batch: usize,
2452 seq: usize,
2453 heads: usize,
2454 state_size: usize,
2455 base: *mut u8,
2456) {
2457 use half::f16;
2458 unsafe {
2459 let read_f16 = |off: usize, len: usize| -> Vec<f32> {
2460 let raw = std::slice::from_raw_parts(base.add(off) as *const u8, len * 2);
2461 raw.chunks_exact(2)
2462 .map(|c| f16::from_le_bytes([c[0], c[1]]).to_f32())
2463 .collect()
2464 };
2465 let write_f16 = |off: usize, data: &[f32]| {
2466 let out = std::slice::from_raw_parts_mut(base.add(off), data.len() * 2);
2467 for (i, &v) in data.iter().enumerate() {
2468 let le = f16::from_f32(v).to_le_bytes();
2469 out[i * 2] = le[0];
2470 out[i * 2 + 1] = le[1];
2471 }
2472 };
2473
2474 let (b, s, h, n) = (batch, seq, heads, state_size);
2475 let q_f = read_f16(q, b * s * h * n);
2476 let k_f = read_f16(k, b * s * h * n);
2477 let v_f = read_f16(v, b * s * h * n);
2478 let g_f = read_f16(g, b * s * h);
2479 let b_f = read_f16(beta, b * s * h);
2480 let mut state_f = if state != 0 {
2481 read_f16(state, b * h * n * n)
2482 } else {
2483 vec![0f32; b * h * n * n]
2484 };
2485 let mut out_f = vec![0f32; b * s * h * n];
2486 let scale = 1.0f32 / (n as f32).sqrt();
2487 let mut sk_buf = vec![0f32; n];
2488 let mut owned_state = vec![0f32; h * n * n];
2489
2490 for bi in 0..b {
2491 let state_slice: &mut [f32] = if state != 0 {
2492 let start = bi * h * n * n;
2493 &mut state_f[start..start + h * n * n]
2494 } else {
2495 owned_state.fill(0.0);
2496 &mut owned_state
2497 };
2498
2499 for ti in 0..s {
2500 let qkv_step_base = bi * s * h * n + ti * h * n;
2501 let gb_step_base = bi * s * h + ti * h;
2502
2503 for hi in 0..h {
2504 let q_row = &q_f[qkv_step_base + hi * n..qkv_step_base + (hi + 1) * n];
2505 let k_row = &k_f[qkv_step_base + hi * n..qkv_step_base + (hi + 1) * n];
2506 let v_row = &v_f[qkv_step_base + hi * n..qkv_step_base + (hi + 1) * n];
2507 let g_t = g_f[gb_step_base + hi];
2508 let beta_t = b_f[gb_step_base + hi];
2509
2510 let s_base = hi * n * n;
2511 let s_mat = &mut state_slice[s_base..s_base + n * n];
2512
2513 let g_exp = g_t.exp();
2514 for st in s_mat.iter_mut() {
2515 *st *= g_exp;
2516 }
2517
2518 for j in 0..n {
2519 let mut acc = 0f32;
2520 for i in 0..n {
2521 acc += s_mat[i * n + j] * k_row[i];
2522 }
2523 sk_buf[j] = acc;
2524 }
2525
2526 for j in 0..n {
2527 sk_buf[j] = (v_row[j] - sk_buf[j]) * beta_t;
2528 }
2529
2530 for i in 0..n {
2531 let ki = k_row[i];
2532 for j in 0..n {
2533 s_mat[i * n + j] += ki * sk_buf[j];
2534 }
2535 }
2536
2537 let out_row = &mut out_f[qkv_step_base + hi * n..qkv_step_base + (hi + 1) * n];
2538 for j in 0..n {
2539 let mut acc = 0f32;
2540 for i in 0..n {
2541 acc += s_mat[i * n + j] * q_row[i];
2542 }
2543 out_row[j] = acc * scale;
2544 }
2545 }
2546 }
2547 }
2548
2549 write_f16(dst, &out_f);
2550 if state != 0 {
2551 write_f16(state, &state_f);
2552 }
2553 }
2554}