1use std::sync::Arc;
4
5use crate::array::{Data, Node, NodeMap, NodeSet, State};
6use crate::gpu::GpuEval;
7use crate::kernels::{self, Operand};
8use crate::primitive::Primitive;
9use crate::{Array, DeviceKind, Error, Result, view};
10
11pub fn eval(arrays: &[Array]) -> Result<()> {
19 schedule(arrays).eval()
20}
21
22pub fn async_eval(arrays: &[Array]) -> Result<()> {
43 schedule(arrays).async_eval()
44}
45
46pub fn schedule(arrays: &[Array]) -> Scheduled {
68 Scheduled {
69 arrays: arrays.to_vec(),
70 order: topological_order(arrays),
71 }
72}
73
74pub struct Scheduled {
76 arrays: Vec<Array>,
77 order: Vec<Arc<Node>>,
78}
79
80impl std::fmt::Debug for Scheduled {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 f.debug_struct("Scheduled")
83 .field("arrays", &self.arrays)
84 .field("nodes", &self.order.len())
85 .finish()
86 }
87}
88
89impl Scheduled {
90 pub fn arrays(&self) -> &[Array] {
92 &self.arrays
93 }
94
95 pub fn async_eval(self) -> Result<()> {
97 let arrays = self.arrays.clone();
98 self.submit().commit();
99 match arrays.iter().find_map(Array::error) {
100 Some(err) => Err(err),
101 None => Ok(()),
102 }
103 }
104
105 pub fn eval(self) -> Result<()> {
107 let arrays = self.arrays.clone();
108 let _ = self.submit().flush();
110 wait(&arrays)
111 }
112
113 fn submit(self) -> GpuEval {
116 let mut gpu = GpuEval::new();
117 for node in self.order {
118 evaluate(&node, &mut gpu);
119 }
122 gpu
123 }
124}
125
126fn wait(arrays: &[Array]) -> Result<()> {
128 for array in arrays {
129 if let Err(err) = array.out().slot.wait() {
130 return Err(err.clone());
131 }
132 }
133 Ok(())
134}
135
136const BFS_MAX_WIDTH: usize = 20;
138
139fn topological_order(arrays: &[Array]) -> Vec<Arc<Node>> {
148 let mut inputs: NodeMap<*const Node, Vec<Arc<Node>>> = NodeMap::default();
150 let mut done = NodeSet::default();
151 let mut stack: Vec<Arc<Node>> = arrays.iter().map(|a| a.node.clone()).collect();
152 while let Some(node) = stack.pop() {
153 let key = Arc::as_ptr(&node);
154 if inputs.contains_key(&key) || done.contains(&key) {
155 continue;
156 }
157 let pending: Vec<Arc<Node>> = match &*node.lock() {
158 State::Pending { inputs, .. } => inputs.iter().map(|a| a.node.clone()).collect(),
159 State::Retained { .. } | State::Done => {
160 done.insert(key);
161 continue;
162 }
163 };
164 stack.extend(pending.iter().cloned());
165 inputs.insert(key, pending);
166 }
167 for list in inputs.values_mut() {
168 list.retain(|n| !done.contains(&Arc::as_ptr(n)));
169 }
170 let roots: Vec<Arc<Node>> = arrays
171 .iter()
172 .map(|a| a.node.clone())
173 .filter(|n| inputs.contains_key(&Arc::as_ptr(n)))
174 .collect();
175 let mut consumers: NodeMap<*const Node, usize> = NodeMap::default();
178 for n in inputs.values().flatten().chain(&roots) {
179 *consumers.entry(Arc::as_ptr(n)).or_default() += 1;
180 }
181 let mut tape: Vec<Option<Arc<Node>>> = vec![None];
183 let mut deferred: Vec<(Option<Arc<Node>>, usize)> = Vec::new();
184 let mut next = 0;
185 while !consumers.is_empty() && (next < tape.len() || !deferred.is_empty()) {
186 let (node, mut j) = if next < tape.len() {
187 next += 1;
188 (tape[next - 1].clone(), 0)
189 } else {
190 deferred.pop().expect("checked above")
191 };
192 let list = match &node {
193 Some(n) => &inputs[&Arc::as_ptr(n)],
194 None => &roots,
195 };
196 while j < list.len() {
197 let key = Arc::as_ptr(&list[j]);
198 if consumers.contains_key(&key) {
199 if tape.len() - next >= BFS_MAX_WIDTH {
200 deferred.push((node.clone(), j));
201 break;
202 }
203 let left = consumers.get_mut(&key).expect("checked above");
204 *left -= 1;
205 if *left == 0 {
206 consumers.remove(&key);
207 tape.push(Some(list[j].clone()));
208 }
209 }
210 j += 1;
211 }
212 }
213 tape.into_iter().rev().flatten().collect()
214}
215
216fn evaluate(node: &Arc<Node>, gpu: &mut GpuEval) {
219 let mut state = node.lock();
220 let (primitive, inputs) = match std::mem::replace(&mut *state, State::Done) {
221 State::Pending { primitive, inputs } => (primitive, inputs),
222 evaluated => {
223 *state = evaluated; return;
225 }
226 };
227 #[cfg(test)]
228 counter::inc();
229 let mut inputs = inputs;
230 let result = match donation(node, &primitive, &mut inputs, gpu) {
231 Some(base) => in_place(node, &primitive, &inputs, base, gpu),
232 None => compute(node, &primitive, &inputs, gpu),
233 };
234 match result {
235 Ok((outputs, true)) => gpu.defer(node.clone(), outputs),
236 Ok((outputs, false)) => {
237 for (output, data) in node.outputs.iter().zip(outputs) {
238 let _ = output.slot.set(Ok(data));
239 }
240 }
241 Err(err) => {
242 for output in &node.outputs {
243 let _ = output.slot.set(Err(err.clone()));
244 }
245 }
246 }
247 if node.tracer && crate::transforms::tracing() {
248 *state = State::Retained { primitive, inputs };
249 return;
250 }
251 drop(state);
252 drop(inputs); }
254
255fn donation(
267 node: &Node,
268 primitive: &Primitive,
269 inputs: &mut [Array],
270 gpu: &GpuEval,
271) -> Option<Data> {
272 let device = node.stream.device;
273 let writes_base = matches!(
274 primitive,
275 Primitive::SliceUpdate { .. }
276 | Primitive::Fast(crate::fast::FastOp::NormRope { cache: true, .. })
277 );
278 if !writes_base || node.tracer || device.kind == DeviceKind::Cpu || !gpu.available(device) {
279 return None;
280 }
281 let base = &mut inputs[0].node;
282 if Arc::strong_count(base) != 1 {
283 return None;
284 }
285 let usable = |data: &Data, out: &crate::array::Output, handles: usize| {
286 out.flags.row_contiguous
287 && crate::gpu::writable(data, out)
288 && Arc::strong_count(&data.buffer) == handles
289 };
290 if let Some(base) = Arc::get_mut(base) {
292 let [out] = base.outputs.as_mut_slice() else {
293 return None;
294 };
295 if let Some(Ok(data)) = out.slot.get() {
296 let handles = 1 + usize::from(out.inflight.get().is_some());
298 if !usable(data, out, handles) {
299 return None;
300 }
301 out.inflight.clear();
302 return match out.slot.take() {
303 Some(Ok(data)) => Some(data),
304 _ => None,
305 };
306 }
307 }
308 let [out] = base.outputs.as_slice() else {
310 return None;
311 };
312 if out.slot.get().is_some() {
313 return None;
314 }
315 let mut state = out.inflight.lock();
316 let crate::array::InFlightState::Set(data) = &*state else {
317 return None;
318 };
319 if !usable(data, out, 1) {
320 return None;
321 }
322 match std::mem::replace(&mut *state, crate::array::InFlightState::Donated) {
323 crate::array::InFlightState::Set(data) => Some(data),
324 _ => None,
325 }
326}
327
328fn in_place(
330 node: &Node,
331 primitive: &Primitive,
332 inputs: &[Array],
333 base: Data,
334 gpu: &mut GpuEval,
335) -> Result<(Vec<Data>, bool)> {
336 let (mut pending, mut foreign) = (false, Vec::new());
337 let rest = inputs[1..]
338 .iter()
339 .map(|a| input_data(a, gpu, &mut pending, &mut foreign))
340 .collect::<Result<Vec<_>>>()?;
341 let operands: Vec<Operand<'_>> = inputs[1..].iter().map(Array::out).zip(&rest).collect();
342 let out = &node.outputs[0];
343 let data = match primitive {
344 Primitive::Fast(op) => gpu.norm_rope_in_place(base, op, &operands, out)?,
345 _ => {
346 let slices = primitive.dim_slices(out.shape.len())?;
347 gpu.slice_update_in_place(base, out, operands[0], &slices)?
348 }
349 };
350 Ok((vec![data], true))
351}
352
353fn input_data<'a>(
356 input: &'a Array,
357 gpu: &mut GpuEval,
358 pending: &mut bool,
359 foreign: &mut Vec<&'a Array>,
360) -> Result<Data> {
361 if let Some(d) = gpu.lookup(&input.node, input.output) {
362 *pending = true;
363 Ok(d)
364 } else if let Some(result) = input.out().slot.get() {
365 result.clone()
366 } else if let Some(d) = input.out().inflight.get() {
367 *pending = true;
370 foreign.push(input);
371 Ok(d)
372 } else {
373 gpu.commit();
376 input.out().slot.wait().clone()
377 }
378}
379
380fn compute(
383 node: &Node,
384 primitive: &Primitive,
385 inputs: &[Array],
386 gpu: &mut GpuEval,
387) -> Result<(Vec<Data>, bool)> {
388 let mut data = Vec::with_capacity(inputs.len());
389 let mut pending = false;
390 let mut foreign = Vec::new();
392 for input in inputs {
393 data.push(input_data(input, gpu, &mut pending, &mut foreign)?);
394 }
395 let operands: Vec<Operand<'_>> = inputs
396 .iter()
397 .zip(&data)
398 .map(|(a, d)| (a.out(), d))
399 .collect();
400 let await_foreign = || -> Result<()> {
403 for input in &foreign {
404 input.out().slot.wait().clone()?;
405 }
406 Ok(())
407 };
408 if let Some(views) = view_outputs(primitive, &operands)? {
409 gpu.depend_on(&foreign);
412 return Ok((views, pending));
413 }
414 let device = node.stream.device;
415 if device.kind != DeviceKind::Cpu {
416 if !gpu.available(device) {
417 return Err(Error::UnsupportedDevice(device));
418 }
419 if gpu.supports(primitive, &operands, &node.outputs) {
420 return Ok((gpu.run(primitive, &operands, &node.outputs)?, true));
421 }
422 }
423 await_foreign()?;
425 if pending {
426 gpu.flush()?;
427 }
428 Ok((cpu(primitive, &operands)?, false))
429}
430
431fn view_outputs(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Option<Vec<Data>>> {
434 let share = |offset| {
435 Some(vec![Data {
436 buffer: operands[0].1.buffer.clone(),
437 offset,
438 }])
439 };
440 Ok(match primitive {
441 Primitive::Transpose(_) | Primitive::Broadcast(_) | Primitive::StopGradient => {
442 share(operands[0].1.offset)
443 }
444 Primitive::Slice { .. } => {
445 let (meta, data) = operands[0];
446 let (_, delta) =
447 view::slice_geometry(&meta.strides, &primitive.dim_slices(meta.shape.len())?);
448 share(data.offset + delta)
449 }
450 Primitive::Custom(call) => Some(
451 operands[call.primals..]
452 .iter()
453 .map(|(_, data)| (*data).clone())
454 .collect(),
455 ),
456 Primitive::Reshape(shape) => {
457 let (meta, data) = operands[0];
458 view::nocopy_reshape(&meta.shape, &meta.strides, shape).and_then(|_| share(data.offset))
459 }
460 _ => None,
461 })
462}
463
464fn cpu(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Vec<Data>> {
466 let new = |buffer| Ok(vec![Data::new(buffer)]);
467 match primitive {
468 Primitive::Unary(op) => new(kernels::unary(*op, operands[0])?),
469 Primitive::Binary(op) => new(kernels::binary(*op, operands[0], operands[1])?),
470 Primitive::Select => new(kernels::select(operands[0], operands[1], operands[2])?),
471 Primitive::AsType(dtype) => new(kernels::cast(operands[0], *dtype)?),
472 Primitive::Reduce { op, axes } => new(kernels::reduce(*op, operands[0], axes)?),
473 Primitive::ArgReduce { op, axis } => new(kernels::arg_reduce(*op, operands[0], *axis)?),
474 Primitive::GatherAxis { axis } => {
475 new(kernels::gather_axis(operands[0], operands[1], *axis)?)
476 }
477 Primitive::ScatterAxis { axis, reduce } => new(kernels::scatter_axis(
478 operands[0],
479 operands[1],
480 operands[2],
481 *axis,
482 *reduce,
483 )?),
484 Primitive::Concatenate { axis } => {
485 let mut shape = operands[0].0.shape.clone();
486 shape[*axis] = operands.iter().map(|(m, _)| m.shape[*axis]).sum();
487 new(kernels::concatenate(operands, &shape, *axis)?)
488 }
489 Primitive::Sort { axis } => new(kernels::sort(operands[0], *axis, false)?),
490 Primitive::ArgSort { axis } => new(kernels::sort(operands[0], *axis, true)?),
491 Primitive::Scan {
492 op,
493 axis,
494 reverse,
495 inclusive,
496 } => new(kernels::scan(
497 *op,
498 operands[0],
499 *axis,
500 *reverse,
501 *inclusive,
502 )?),
503 Primitive::Matmul => new(kernels::matmul(operands[0], operands[1])?),
504 Primitive::AddMM { .. } => {
505 let (alpha, beta) = primitive.addmm_factors();
506 new(kernels::addmm(
507 operands[0],
508 operands[1],
509 operands[2],
510 alpha,
511 beta,
512 )?)
513 }
514 Primitive::RandomBits { shape, .. } => {
515 let n: usize = shape.iter().product();
516 new(kernels::random_bits(
517 operands[0],
518 n * primitive_width(primitive),
519 )?)
520 }
521 Primitive::Fast(op) => op.eval_cpu(operands),
522 Primitive::MetalKernel(_) => Err(Error::InvalidArgument(
523 "metal_kernel: custom kernels run on the GPU only".into(),
524 )),
525 Primitive::Compiled(fused) => fused.eval_cpu(operands),
526 Primitive::Quantize { group_size, bits } => {
527 let (packed, scales, biases) =
528 kernels::quantize(operands[0], *group_size, usize::from(*bits))?;
529 Ok(vec![
530 Data::new(packed),
531 Data::new(scales),
532 Data::new(biases),
533 ])
534 }
535 Primitive::Dequantize { group_size, bits } => new(kernels::dequantize(
536 operands[0],
537 operands[1],
538 operands[2],
539 *group_size,
540 usize::from(*bits),
541 )?),
542 Primitive::QuantizedMatmul {
543 group_size,
544 bits,
545 transpose,
546 } => new(kernels::quantized_matmul(
547 operands,
548 *group_size,
549 usize::from(*bits),
550 *transpose,
551 )?),
552 Primitive::SliceUpdate { .. } => new(kernels::slice_update(
553 operands[0],
554 operands[1],
555 &primitive.dim_slices(operands[0].0.shape.len())?,
556 )?),
557 Primitive::Reshape(_) => new(kernels::contiguous(operands[0].0, operands[0].1)?),
559 Primitive::Transpose(_)
560 | Primitive::Broadcast(_)
561 | Primitive::Slice { .. }
562 | Primitive::StopGradient
563 | Primitive::Custom(_) => {
564 unreachable!("views are handled by view_outputs")
565 }
566 #[cfg(test)]
567 Primitive::AddMultiply => {
568 use crate::primitive::BinaryOp;
569 Ok(vec![
570 Data::new(kernels::binary(BinaryOp::Add, operands[0], operands[1])?),
571 Data::new(kernels::binary(
572 BinaryOp::Multiply,
573 operands[0],
574 operands[1],
575 )?),
576 ])
577 }
578 }
579}
580
581fn primitive_width(primitive: &Primitive) -> usize {
582 match primitive {
583 Primitive::RandomBits { width, .. } => usize::from(*width),
584 _ => unreachable!(),
585 }
586}
587
588#[cfg(test)]
590pub(crate) mod counter {
591 use std::cell::Cell;
592
593 thread_local!(static EVALUATED: Cell<usize> = const { Cell::new(0) });
594
595 pub(crate) fn inc() {
596 EVALUATED.set(EVALUATED.get() + 1);
597 }
598 pub(crate) fn take() -> usize {
599 EVALUATED.replace(0)
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606 use crate::buffer::live;
607
608 #[test]
609 fn shared_node_runs_once() {
610 let x = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
611 let shared = &x + &x;
612 let left = &shared * &x;
613 let right = &shared + &x;
614 let top = &left + &right;
615 counter::take();
616 eval(&[top.clone(), left.clone()]).unwrap();
617 assert_eq!(counter::take(), 4, "shared, left, right, top: once each");
618 top.eval().unwrap();
619 assert_eq!(counter::take(), 0, "already evaluated");
620 assert_eq!(top.to_vec::<f32>().unwrap(), vec![5.0, 14.0]);
621 }
622
623 #[test]
624 fn intermediates_freed() {
625 crate::set_default_stream(crate::Stream::default_for(crate::Device::cpu()));
628 let before = live::count();
629 let mut x = Array::scalar(0i64);
630 let one = Array::scalar(1i64);
631 let steps = if cfg!(miri) { 100 } else { 10_000 };
632 for _ in 0..steps {
633 x = &x + &one;
634 }
635 x.eval().unwrap();
636 drop(one);
637 assert_eq!(
638 live::count() - before,
639 1,
640 "only the result's buffer is alive"
641 );
642 assert_eq!(x.item::<i64>().unwrap(), steps as i64);
643 }
644
645 #[test]
646 fn multi_output_once() {
647 let a = Array::from_slice(&[2i32, 3], &[2]).unwrap();
648 let b = Array::from_slice(&[5i32, 7], &[2]).unwrap();
649 let mut outs = Array::from_op(Primitive::AddMultiply, vec![a, b]).unwrap();
650 let product = outs.pop().unwrap();
651 let sum = outs.pop().unwrap();
652 counter::take();
653 eval(&[sum.clone(), product.clone()]).unwrap();
654 assert_eq!(counter::take(), 1);
655 drop(sum);
656 assert_eq!(product.to_vec::<i32>().unwrap(), vec![10, 21]);
657 }
658
659 #[test]
660 fn slice_update_writes_a_strided_region() {
661 let base = crate::arange(0.0, 12.0, 1.0, crate::DType::I32)
663 .unwrap()
664 .reshape(&[4, 3])
665 .unwrap()
666 .t()
667 .unwrap(); let update = Array::from_slice(&[-1i32, -2, -3, -4, -5, -6, -7, -8], &[2, 4]).unwrap();
669 let primitive = Primitive::SliceUpdate {
670 start: vec![0, 3],
671 step: vec![2, -1],
672 len: vec![2, 4],
673 };
674 let out = Array::from_op(primitive.clone(), vec![base.clone(), update.clone()])
675 .unwrap()
676 .remove(0);
677 assert_eq!(
678 out.to_vec::<i32>().unwrap(),
679 [-4, -3, -2, -1, 1, 4, 7, 10, -8, -7, -6, -5]
680 );
681 let wrong = Array::from_slice(&[0i32; 3], &[3]).unwrap();
682 assert!(Array::from_op(primitive, vec![base, wrong]).is_err());
683 }
684
685 #[test]
686 fn tracer_nodes_keep_their_graph_only_while_tracing() {
687 let x = Array::scalar(2.0f32);
688 let kept = std::cell::Cell::new(false);
689 let g = crate::grad(|x: &Array| {
690 let y = x * x;
691 y.eval()?;
692 kept.set(matches!(&*y.node.lock(), State::Retained { .. }));
693 Ok(y)
694 });
695 assert_eq!(g(&x).unwrap().item::<f32>().unwrap(), 4.0);
696 assert!(kept.get(), "retained inside a transform");
697 let traced = crate::vjp(
698 |xs| Ok(vec![&xs[0] * &xs[0]]),
699 &[x],
700 &[Array::scalar(1.0f32)],
701 )
702 .unwrap()
703 .0
704 .remove(0);
705 traced.eval().unwrap();
706 assert!(
707 matches!(&*traced.node.lock(), State::Done),
708 "outside a transform"
709 );
710 }
711
712 #[test]
713 fn evaluation_detaches_inputs() {
714 let a = Array::scalar(1u8);
715 let c = &a + &a;
716 assert!(c.primitive().is_some());
717 c.eval().unwrap();
718 assert_eq!(c.primitive(), None);
719 assert_eq!(Arc::strong_count(&a.node), 1, "c no longer references a");
720 }
721
722 fn positions(arrays: &[Array]) -> NodeMap<*const Node, usize> {
724 let order = topological_order(arrays);
725 let at: NodeMap<*const Node, usize> = order
726 .iter()
727 .enumerate()
728 .map(|(i, n)| (Arc::as_ptr(n), i))
729 .collect();
730 assert_eq!(at.len(), order.len(), "each node once");
731 for (i, node) in order.iter().enumerate() {
732 if let State::Pending { inputs, .. } = &*node.lock() {
733 for input in inputs {
734 if let Some(&j) = at.get(&Arc::as_ptr(&input.node)) {
735 assert!(j < i, "inputs come first");
736 }
737 }
738 }
739 }
740 at
741 }
742
743 #[test]
744 fn independent_work_is_ordered_side_by_side() {
745 let x = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
746 let (a1, b1) = (&x + 1.0, &x + 3.0);
747 let (a2, b2) = (&a1 * 2.0, &b1 * 4.0);
748 let top = &a2 + &b2;
749 let at = positions(std::slice::from_ref(&top));
750 let p = |a: &Array| at[&Arc::as_ptr(&a.node)];
751 assert!(
752 p(&a1).max(p(&b1)) < p(&a2).min(p(&b2)),
753 "both chains advance together"
754 );
755 assert_eq!(top.to_vec::<f32>().unwrap(), [20.0, 26.0]);
756 }
757
758 #[test]
759 fn wide_and_shared_graphs_are_ordered() {
760 let x = Array::from_slice(&[1i32], &[1]).unwrap();
761 let one = Array::from_slice(&[1i32], &[1]).unwrap();
762 let shared = &x + &x;
763 let mut joined = shared.clone();
765 for i in 0..100 {
766 let mut c = &shared + &Array::from_slice(&[i], &[1]).unwrap();
767 for _ in 0..i % 7 {
768 c = &c * &one;
769 }
770 joined = &joined + &c;
771 }
772 positions(&[joined.clone(), shared.clone(), joined.clone()]);
773 counter::take();
774 eval(&[joined.clone(), shared.clone()]).unwrap();
775 let nodes = 1 + 100 * 2 + (0..100).map(|i| i % 7).sum::<usize>();
776 assert_eq!(counter::take(), nodes, "every node once");
777 assert_eq!(
778 joined.item::<i32>().unwrap(),
779 2 + 100 * 2 + (0..100).sum::<i32>()
780 );
781 }
782
783 #[test]
784 fn scheduled_evaluation_skips_work_done_meanwhile() {
785 let x = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
786 let shared = &x * &x;
787 let top = &shared + &x;
788 let scheduled = schedule(std::slice::from_ref(&top));
789 assert_eq!(scheduled.arrays().len(), 1);
790 counter::take();
791 shared.eval().unwrap();
792 scheduled.eval().unwrap();
793 assert_eq!(counter::take(), 2, "shared once, then top");
794 assert_eq!(top.to_vec::<f32>().unwrap(), [2.0, 6.0]);
795 }
796}