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