1use crate::thunk::{ScanBodyPlan, compile_scan_body, execute_scan_host};
15use rlx_ir::{Graph, NodeId, Op};
16use std::sync::Arc;
17
18#[derive(Clone)]
21pub struct ScanHostDesc {
22 pub plan: Arc<ScanBodyPlan>,
23 pub outer_init_off: usize,
24 pub outer_final_off: usize,
25 pub length: u32,
26 pub save_trajectory: bool,
27 pub xs_outer: Vec<(usize, usize)>,
29 pub bcast_outer: Vec<(usize, usize)>,
31}
32
33impl std::fmt::Debug for ScanHostDesc {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.debug_struct("ScanHostDesc")
36 .field("carry_bytes", &self.plan.carry_bytes)
37 .field("length", &self.length)
38 .field("save_trajectory", &self.save_trajectory)
39 .field("num_xs", &self.xs_outer.len())
40 .field("num_bcast", &self.bcast_outer.len())
41 .finish()
42 }
43}
44
45pub fn scan_host_desc_from_node(
48 graph: &Graph,
49 node: &rlx_ir::Node,
50 mut byte_off: impl FnMut(NodeId) -> usize,
51) -> ScanHostDesc {
52 let (body, length, save_trajectory, num_bcast, num_xs) = match &node.op {
53 Op::Scan {
54 body,
55 length,
56 save_trajectory,
57 num_bcast,
58 num_xs,
59 ..
60 } => (
61 body,
62 *length,
63 *save_trajectory,
64 *num_bcast as usize,
65 *num_xs as usize,
66 ),
67 _ => panic!("scan_host_desc_from_node: expected Op::Scan"),
68 };
69 let plan = compile_scan_body(body, num_bcast, num_xs);
70 let bcast_outer: Vec<(usize, usize)> = (0..num_bcast)
71 .map(|i| {
72 let id = node.inputs[1 + i];
73 (byte_off(id), graph.node(id).shape.size_bytes().unwrap())
74 })
75 .collect();
76 let xs_outer: Vec<(usize, usize)> = (0..num_xs)
77 .map(|i| {
78 let id = node.inputs[1 + num_bcast + i];
79 let total = graph.node(id).shape.size_bytes().unwrap();
80 (byte_off(id), total / length as usize)
81 })
82 .collect();
83 ScanHostDesc {
84 plan: Arc::new(plan),
85 outer_init_off: byte_off(node.inputs[0]),
86 outer_final_off: byte_off(node.id),
87 length,
88 save_trajectory,
89 xs_outer,
90 bcast_outer,
91 }
92}
93
94pub unsafe fn execute_scan_host_desc(base: *mut u8, desc: &ScanHostDesc) {
99 unsafe {
100 execute_scan_host(
101 base,
102 &desc.plan,
103 desc.outer_init_off,
104 desc.outer_final_off,
105 desc.length,
106 desc.save_trajectory,
107 &desc.xs_outer,
108 &desc.bcast_outer,
109 );
110 }
111}
112
113#[derive(Clone)]
116pub struct ScanHostSpan {
117 pub lo: usize,
118 pub hi: usize,
119 pub desc: ScanHostDesc,
120}
121
122impl ScanHostSpan {
123 pub fn from_desc(desc: ScanHostDesc) -> Self {
124 let cb = desc.plan.carry_bytes;
125 let out_len = if desc.save_trajectory {
126 desc.length as usize * cb
127 } else {
128 cb
129 };
130 let mut lo = desc.outer_init_off.min(desc.outer_final_off);
131 let mut hi = (desc.outer_init_off + cb).max(desc.outer_final_off + out_len);
132 for &(o, t) in &desc.bcast_outer {
133 lo = lo.min(o);
134 hi = hi.max(o + t);
135 }
136 for &(o, ps) in &desc.xs_outer {
137 lo = lo.min(o);
138 hi = hi.max(o + desc.length as usize * ps);
139 }
140 let rebated = ScanHostDesc {
141 plan: desc.plan,
142 outer_init_off: desc.outer_init_off - lo,
143 outer_final_off: desc.outer_final_off - lo,
144 length: desc.length,
145 save_trajectory: desc.save_trajectory,
146 xs_outer: desc.xs_outer.iter().map(|&(o, ps)| (o - lo, ps)).collect(),
147 bcast_outer: desc.bcast_outer.iter().map(|&(o, t)| (o - lo, t)).collect(),
148 };
149 Self {
150 lo,
151 hi,
152 desc: rebated,
153 }
154 }
155
156 pub fn len(&self) -> usize {
157 self.hi - self.lo
158 }
159
160 pub fn is_empty(&self) -> bool {
161 self.len() == 0
162 }
163}
164
165pub fn eval_single_op_f32(
169 op: &Op,
170 out_shape: &rlx_ir::Shape,
171 inputs: &[(rlx_ir::Shape, &[f32])],
172) -> Vec<f32> {
173 let n = out_shape
174 .num_elements()
175 .unwrap_or(0)
176 .max(out_shape.size_bytes().unwrap_or(0).div_ceil(4));
177 if let Some(y) = eval_elementwise_fast(op, n, inputs) {
181 return y;
182 }
183 fn host_shape(sh: &rlx_ir::Shape) -> rlx_ir::Shape {
194 match sh.dtype() {
195 rlx_ir::DType::C64 | rlx_ir::DType::C128 | rlx_ir::DType::F64 => sh.clone(),
196 _ => sh.clone().with_dtype(rlx_ir::DType::F32),
197 }
198 }
199 let mut g = Graph::new("scan_eval_single_op");
200 let ids: Vec<NodeId> = inputs
201 .iter()
202 .enumerate()
203 .map(|(i, (sh, _))| {
204 let sh_use = host_shape(sh);
205 g.append_node(
206 Op::Input {
207 name: format!("in{i}"),
208 },
209 vec![],
210 sh_use,
211 None,
212 )
213 })
214 .collect();
215 let out_use = host_shape(out_shape);
216 let out = g.append_node(op.clone(), ids.clone(), out_use.clone(), None);
217 g.set_outputs(vec![out]);
218 let plan = rlx_opt::memory::plan_memory_aligned(&g, 16);
219 let mut arena = crate::arena::Arena::from_plan(plan);
220 for (i, (_, vals)) in inputs.iter().enumerate() {
221 let slot = arena.slice_mut(ids[i]);
222 let n = slot.len().min(vals.len());
223 slot[..n].copy_from_slice(&vals[..n]);
224 }
225 let schedule = crate::thunk::compile_thunks(&g, &arena);
226 crate::thunk::execute_thunks(&schedule, arena.raw_buf_mut());
227 let n = out_use
228 .size_bytes()
229 .unwrap_or(0)
230 .div_ceil(4)
231 .max(out_use.num_elements().unwrap_or(0));
232 arena.slice_mut(out)[..n].to_vec()
233}
234
235fn eval_elementwise_fast(
236 op: &Op,
237 n: usize,
238 inputs: &[(rlx_ir::Shape, &[f32])],
239) -> Option<Vec<f32>> {
240 use crate::thunk::{apply_activation_inplace, region_binary_scalar};
241 use rayon::prelude::*;
242 if n == 0 {
243 return Some(Vec::new());
244 }
245 match op {
246 Op::Binary(bop) if inputs.len() == 2 => {
247 let a = inputs[0].1;
248 let b = inputs[1].1;
249 let a_n = a.len().max(1);
250 let b_n = b.len().max(1);
251 let mut y = vec![0f32; n];
252 if n < 4096 {
253 if a_n == n && b_n == n {
254 for i in 0..n {
255 y[i] = region_binary_scalar(*bop, a[i], b[i]);
256 }
257 } else if a_n == 1 && b_n == n {
258 let av = a[0];
259 for i in 0..n {
260 y[i] = region_binary_scalar(*bop, av, b[i]);
261 }
262 } else if a_n == n && b_n == 1 {
263 let bv = b[0];
264 for i in 0..n {
265 y[i] = region_binary_scalar(*bop, a[i], bv);
266 }
267 } else {
268 for i in 0..n {
269 y[i] = region_binary_scalar(*bop, a[i % a_n], b[i % b_n]);
270 }
271 }
272 } else if a_n == n && b_n == n {
273 y.par_iter_mut().enumerate().for_each(|(i, out)| {
274 *out = region_binary_scalar(*bop, a[i], b[i]);
275 });
276 } else if a_n == 1 && b_n == n {
277 let av = a[0];
278 y.par_iter_mut().enumerate().for_each(|(i, out)| {
279 *out = region_binary_scalar(*bop, av, b[i]);
280 });
281 } else if a_n == n && b_n == 1 {
282 let bv = b[0];
283 y.par_iter_mut().enumerate().for_each(|(i, out)| {
284 *out = region_binary_scalar(*bop, a[i], bv);
285 });
286 } else {
287 y.par_iter_mut().enumerate().for_each(|(i, out)| {
289 *out = region_binary_scalar(*bop, a[i % a_n], b[i % b_n]);
290 });
291 }
292 Some(y)
293 }
294 Op::Activation(act) if inputs.len() == 1 => {
295 let src = inputs[0].1;
296 let mut y = if src.len() >= n {
297 src[..n].to_vec()
298 } else if src.len() == 1 {
299 vec![src[0]; n]
300 } else {
301 (0..n).map(|i| src[i % src.len().max(1)]).collect()
302 };
303 apply_activation_inplace(&mut y, *act);
304 Some(y)
305 }
306 Op::Where if inputs.len() == 3 => {
307 let c = inputs[0].1;
308 let t = inputs[1].1;
309 let f = inputs[2].1;
310 let cn = c.len().max(1);
311 let tn = t.len().max(1);
312 let fn_ = f.len().max(1);
313 let mut y = vec![0f32; n];
314 y.par_iter_mut().enumerate().for_each(|(i, out)| {
315 *out = if c[i % cn] != 0.0 {
316 t[i % tn]
317 } else {
318 f[i % fn_]
319 };
320 });
321 Some(y)
322 }
323 Op::Fma if inputs.len() == 3 => {
324 let a = inputs[0].1;
325 let b = inputs[1].1;
326 let c = inputs[2].1;
327 let an = a.len().max(1);
328 let bn = b.len().max(1);
329 let cn = c.len().max(1);
330 let mut y = vec![0f32; n];
331 y.par_iter_mut().enumerate().for_each(|(i, out)| {
332 *out = a[i % an].mul_add(b[i % bn], c[i % cn]);
333 });
334 Some(y)
335 }
336 _ => None,
337 }
338}
339
340pub fn maybe_unroll_scans(graph: Graph, max_length: u32) -> Graph {
342 rlx_opt::control_flow::maybe_unroll_scans(graph, max_length)
343}
344
345#[allow(clippy::too_many_arguments)]
349pub fn run_scan_packed_f32(
350 body: &Graph,
351 length: u32,
352 save_trajectory: bool,
353 num_bcast: usize,
354 num_xs: usize,
355 init: &[f32],
356 bcasts: &[Vec<f32>],
357 xs: &[Vec<f32>],
358 out_elems: usize,
359) -> Vec<f32> {
360 assert_eq!(bcasts.len(), num_bcast);
361 assert_eq!(xs.len(), num_xs);
362 let plan = compile_scan_body(body, num_bcast, num_xs);
363 let mut arena: Vec<u8> = Vec::new();
364 let init_off = arena.len();
365 arena.extend(init.iter().flat_map(|f| f.to_le_bytes()));
366 let mut bcast_outer: Vec<(usize, usize)> = Vec::with_capacity(num_bcast);
367 for v in bcasts {
368 let off = arena.len();
369 arena.extend(v.iter().flat_map(|f| f.to_le_bytes()));
370 bcast_outer.push((off, v.len() * 4));
371 }
372 let mut xs_outer: Vec<(usize, usize)> = Vec::with_capacity(num_xs);
373 for v in xs {
374 let total = v.len() * 4;
375 let off = arena.len();
376 arena.extend(v.iter().flat_map(|f| f.to_le_bytes()));
377 xs_outer.push((off, total / length as usize));
378 }
379 let final_off = arena.len();
380 arena.resize(final_off + out_elems * 4, 0);
381 let desc = ScanHostDesc {
382 plan: Arc::new(plan),
383 outer_init_off: init_off,
384 outer_final_off: final_off,
385 length,
386 save_trajectory,
387 xs_outer,
388 bcast_outer,
389 };
390 unsafe {
391 execute_scan_host_desc(arena.as_mut_ptr(), &desc);
392 }
393 arena[final_off..final_off + out_elems * 4]
394 .as_chunks::<4>()
395 .0
396 .iter()
397 .map(|c| f32::from_le_bytes(*c))
398 .collect()
399}
400
401#[macro_export]
403macro_rules! rlx_scan_host_desc {
404 ($graph:expr, $node:expr, $byte_off:expr) => {
405 $crate::thunk::scan_host_desc_from_node(&$graph, $node, $byte_off)
406 };
407}
408
409#[macro_export]
411macro_rules! rlx_execute_scan_on_bytes {
412 ($base:expr, $desc:expr) => {
413 $crate::thunk::execute_scan_host_desc($base, $desc)
414 };
415}
416
417#[macro_export]
422macro_rules! rlx_arena_stage_d2h {
423 (
424 arena_size_bytes = $nbytes:expr,
425 sync = $sync:block,
426 dtoh = |$host:ident| $dtoh:block,
427 on_host = |$host_mut:ident| $on_host:block,
428 htod = |$host_back:ident| $htod:block $(,)?
429 ) => {{
430 let n_f32 = ($nbytes) / 4;
431 $sync
432 let mut $host = vec![0f32; n_f32];
433 $dtoh
434 {
435 let $host_mut: &mut [f32] = &mut $host[..];
436 $on_host
437 }
438 let $host_back = $host;
439 $htod
440 }};
441}
442
443#[macro_export]
445macro_rules! rlx_scan_stage_d2h {
446 (
447 arena_size_bytes = $nbytes:expr,
448 desc = $desc:expr,
449 sync = $sync:block,
450 dtoh = |$host:ident| $dtoh:block,
451 htod = |$host_back:ident| $htod:block $(,)?
452 ) => {
453 $crate::rlx_arena_stage_d2h! {
454 arena_size_bytes = $nbytes,
455 sync = $sync,
456 dtoh = |$host| $dtoh,
457 on_host = |host_mut| {
458 unsafe {
459 $crate::thunk::execute_scan_host_desc(
460 host_mut.as_mut_ptr() as *mut u8,
461 $desc,
462 );
463 }
464 },
465 htod = |$host_back| $htod,
466 }
467 };
468}
469
470#[macro_export]
472macro_rules! rlx_maybe_unroll_scans {
473 ($graph:expr, $max_length:expr) => {
474 $crate::thunk::maybe_unroll_scans($graph, $max_length)
475 };
476}