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 if !matches!(primitive, Primitive::SliceUpdate { .. })
274 || node.tracer
275 || device.kind == DeviceKind::Cpu
276 || !gpu.available(device)
277 {
278 return None;
279 }
280 let base = &mut inputs[0].node;
281 if Arc::strong_count(base) != 1 {
282 return None;
283 }
284 let usable = |data: &Data, out: &crate::array::Output, handles: usize| {
285 out.flags.row_contiguous
286 && crate::gpu::writable(data, out)
287 && Arc::strong_count(&data.buffer) == handles
288 };
289 if let Some(base) = Arc::get_mut(base) {
291 let [out] = base.outputs.as_mut_slice() else {
292 return None;
293 };
294 if let Some(Ok(data)) = out.slot.get() {
295 let handles = 1 + usize::from(out.inflight.get().is_some());
297 if !usable(data, out, handles) {
298 return None;
299 }
300 out.inflight.clear();
301 return match out.slot.take() {
302 Some(Ok(data)) => Some(data),
303 _ => None,
304 };
305 }
306 }
307 let [out] = base.outputs.as_slice() else {
309 return None;
310 };
311 if out.slot.get().is_some() {
312 return None;
313 }
314 let mut state = out.inflight.lock();
315 let crate::array::InFlightState::Set(data) = &*state else {
316 return None;
317 };
318 if !usable(data, out, 1) {
319 return None;
320 }
321 match std::mem::replace(&mut *state, crate::array::InFlightState::Donated) {
322 crate::array::InFlightState::Set(data) => Some(data),
323 _ => None,
324 }
325}
326
327fn in_place(
329 node: &Node,
330 primitive: &Primitive,
331 inputs: &[Array],
332 base: Data,
333 gpu: &mut GpuEval,
334) -> Result<(Vec<Data>, bool)> {
335 let (mut pending, mut foreign) = (false, Vec::new());
336 let update = input_data(&inputs[1], gpu, &mut pending, &mut foreign)?;
337 let slices = primitive.dim_slices(node.outputs[0].shape.len())?;
338 let out = &node.outputs[0];
339 let data = gpu.slice_update_in_place(base, out, (inputs[1].out(), &update), &slices)?;
340 Ok((vec![data], true))
341}
342
343fn input_data<'a>(
346 input: &'a Array,
347 gpu: &mut GpuEval,
348 pending: &mut bool,
349 foreign: &mut Vec<&'a Array>,
350) -> Result<Data> {
351 if let Some(d) = gpu.lookup(&input.node, input.output) {
352 *pending = true;
353 Ok(d)
354 } else if let Some(result) = input.out().slot.get() {
355 result.clone()
356 } else if let Some(d) = input.out().inflight.get() {
357 *pending = true;
360 foreign.push(input);
361 Ok(d)
362 } else {
363 gpu.commit();
366 input.out().slot.wait().clone()
367 }
368}
369
370fn compute(
373 node: &Node,
374 primitive: &Primitive,
375 inputs: &[Array],
376 gpu: &mut GpuEval,
377) -> Result<(Vec<Data>, bool)> {
378 let mut data = Vec::with_capacity(inputs.len());
379 let mut pending = false;
380 let mut foreign = Vec::new();
382 for input in inputs {
383 data.push(input_data(input, gpu, &mut pending, &mut foreign)?);
384 }
385 let operands: Vec<Operand<'_>> = inputs
386 .iter()
387 .zip(&data)
388 .map(|(a, d)| (a.out(), d))
389 .collect();
390 let await_foreign = || -> Result<()> {
393 for input in &foreign {
394 input.out().slot.wait().clone()?;
395 }
396 Ok(())
397 };
398 if let Some(views) = view_outputs(primitive, &operands)? {
399 gpu.depend_on(&foreign);
402 return Ok((views, pending));
403 }
404 let device = node.stream.device;
405 if device.kind != DeviceKind::Cpu {
406 if !gpu.available(device) {
407 return Err(Error::UnsupportedDevice(device));
408 }
409 if gpu.supports(primitive, &operands, &node.outputs) {
410 return Ok((gpu.run(primitive, &operands, &node.outputs)?, true));
411 }
412 }
413 await_foreign()?;
415 if pending {
416 gpu.flush()?;
417 }
418 Ok((cpu(primitive, &operands)?, false))
419}
420
421fn view_outputs(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Option<Vec<Data>>> {
424 let share = |offset| {
425 Some(vec![Data {
426 buffer: operands[0].1.buffer.clone(),
427 offset,
428 }])
429 };
430 Ok(match primitive {
431 Primitive::Transpose(_) | Primitive::Broadcast(_) | Primitive::StopGradient => {
432 share(operands[0].1.offset)
433 }
434 Primitive::Slice { .. } => {
435 let (meta, data) = operands[0];
436 let (_, delta) =
437 view::slice_geometry(&meta.strides, &primitive.dim_slices(meta.shape.len())?);
438 share(data.offset + delta)
439 }
440 Primitive::Custom(call) => Some(
441 operands[call.primals..]
442 .iter()
443 .map(|(_, data)| (*data).clone())
444 .collect(),
445 ),
446 Primitive::Reshape(shape) => {
447 let (meta, data) = operands[0];
448 view::nocopy_reshape(&meta.shape, &meta.strides, shape).and_then(|_| share(data.offset))
449 }
450 _ => None,
451 })
452}
453
454fn cpu(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Vec<Data>> {
456 let new = |buffer| Ok(vec![Data::new(buffer)]);
457 match primitive {
458 Primitive::Unary(op) => new(kernels::unary(*op, operands[0])?),
459 Primitive::Binary(op) => new(kernels::binary(*op, operands[0], operands[1])?),
460 Primitive::Select => new(kernels::select(operands[0], operands[1], operands[2])?),
461 Primitive::AsType(dtype) => new(kernels::cast(operands[0], *dtype)?),
462 Primitive::Reduce { op, axes } => new(kernels::reduce(*op, operands[0], axes)?),
463 Primitive::ArgReduce { op, axis } => new(kernels::arg_reduce(*op, operands[0], *axis)?),
464 Primitive::GatherAxis { axis } => {
465 new(kernels::gather_axis(operands[0], operands[1], *axis)?)
466 }
467 Primitive::ScatterAxis { axis, reduce } => new(kernels::scatter_axis(
468 operands[0],
469 operands[1],
470 operands[2],
471 *axis,
472 *reduce,
473 )?),
474 Primitive::Concatenate { axis } => {
475 let mut shape = operands[0].0.shape.clone();
476 shape[*axis] = operands.iter().map(|(m, _)| m.shape[*axis]).sum();
477 new(kernels::concatenate(operands, &shape, *axis)?)
478 }
479 Primitive::Sort { axis } => new(kernels::sort(operands[0], *axis, false)?),
480 Primitive::ArgSort { axis } => new(kernels::sort(operands[0], *axis, true)?),
481 Primitive::Scan {
482 op,
483 axis,
484 reverse,
485 inclusive,
486 } => new(kernels::scan(
487 *op,
488 operands[0],
489 *axis,
490 *reverse,
491 *inclusive,
492 )?),
493 Primitive::Matmul => new(kernels::matmul(operands[0], operands[1])?),
494 Primitive::AddMM { .. } => {
495 let (alpha, beta) = primitive.addmm_factors();
496 new(kernels::addmm(
497 operands[0],
498 operands[1],
499 operands[2],
500 alpha,
501 beta,
502 )?)
503 }
504 Primitive::RandomBits { shape, .. } => {
505 let n: usize = shape.iter().product();
506 new(kernels::random_bits(
507 operands[0],
508 n * primitive_width(primitive),
509 )?)
510 }
511 Primitive::Fast(op) => op.eval_cpu(operands),
512 Primitive::MetalKernel(_) => Err(Error::InvalidArgument(
513 "metal_kernel: custom kernels run on the GPU only".into(),
514 )),
515 Primitive::Compiled(fused) => fused.eval_cpu(operands),
516 Primitive::Quantize { group_size, bits } => {
517 let (packed, scales, biases) =
518 kernels::quantize(operands[0], *group_size, usize::from(*bits))?;
519 Ok(vec![
520 Data::new(packed),
521 Data::new(scales),
522 Data::new(biases),
523 ])
524 }
525 Primitive::Dequantize { group_size, bits } => new(kernels::dequantize(
526 operands[0],
527 operands[1],
528 operands[2],
529 *group_size,
530 usize::from(*bits),
531 )?),
532 Primitive::QuantizedMatmul {
533 group_size,
534 bits,
535 transpose,
536 } => new(kernels::quantized_matmul(
537 operands,
538 *group_size,
539 usize::from(*bits),
540 *transpose,
541 )?),
542 Primitive::SliceUpdate { .. } => new(kernels::slice_update(
543 operands[0],
544 operands[1],
545 &primitive.dim_slices(operands[0].0.shape.len())?,
546 )?),
547 Primitive::Reshape(_) => new(kernels::contiguous(operands[0].0, operands[0].1)?),
549 Primitive::Transpose(_)
550 | Primitive::Broadcast(_)
551 | Primitive::Slice { .. }
552 | Primitive::StopGradient
553 | Primitive::Custom(_) => {
554 unreachable!("views are handled by view_outputs")
555 }
556 #[cfg(test)]
557 Primitive::AddMultiply => {
558 use crate::primitive::BinaryOp;
559 Ok(vec![
560 Data::new(kernels::binary(BinaryOp::Add, operands[0], operands[1])?),
561 Data::new(kernels::binary(
562 BinaryOp::Multiply,
563 operands[0],
564 operands[1],
565 )?),
566 ])
567 }
568 }
569}
570
571fn primitive_width(primitive: &Primitive) -> usize {
572 match primitive {
573 Primitive::RandomBits { width, .. } => usize::from(*width),
574 _ => unreachable!(),
575 }
576}
577
578#[cfg(test)]
580pub(crate) mod counter {
581 use std::cell::Cell;
582
583 thread_local!(static EVALUATED: Cell<usize> = const { Cell::new(0) });
584
585 pub(crate) fn inc() {
586 EVALUATED.set(EVALUATED.get() + 1);
587 }
588 pub(crate) fn take() -> usize {
589 EVALUATED.replace(0)
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596 use crate::buffer::live;
597
598 #[test]
599 fn shared_node_runs_once() {
600 let x = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
601 let shared = &x + &x;
602 let left = &shared * &x;
603 let right = &shared + &x;
604 let top = &left + &right;
605 counter::take();
606 eval(&[top.clone(), left.clone()]).unwrap();
607 assert_eq!(counter::take(), 4, "shared, left, right, top: once each");
608 top.eval().unwrap();
609 assert_eq!(counter::take(), 0, "already evaluated");
610 assert_eq!(top.to_vec::<f32>().unwrap(), vec![5.0, 14.0]);
611 }
612
613 #[test]
614 fn intermediates_freed() {
615 crate::set_default_stream(crate::Stream::default_for(crate::Device::cpu()));
618 let before = live::count();
619 let mut x = Array::scalar(0i64);
620 let one = Array::scalar(1i64);
621 let steps = if cfg!(miri) { 100 } else { 10_000 };
622 for _ in 0..steps {
623 x = &x + &one;
624 }
625 x.eval().unwrap();
626 drop(one);
627 assert_eq!(
628 live::count() - before,
629 1,
630 "only the result's buffer is alive"
631 );
632 assert_eq!(x.item::<i64>().unwrap(), steps as i64);
633 }
634
635 #[test]
636 fn multi_output_once() {
637 let a = Array::from_slice(&[2i32, 3], &[2]).unwrap();
638 let b = Array::from_slice(&[5i32, 7], &[2]).unwrap();
639 let mut outs = Array::from_op(Primitive::AddMultiply, vec![a, b]).unwrap();
640 let product = outs.pop().unwrap();
641 let sum = outs.pop().unwrap();
642 counter::take();
643 eval(&[sum.clone(), product.clone()]).unwrap();
644 assert_eq!(counter::take(), 1);
645 drop(sum);
646 assert_eq!(product.to_vec::<i32>().unwrap(), vec![10, 21]);
647 }
648
649 #[test]
650 fn slice_update_writes_a_strided_region() {
651 let base = crate::arange(0.0, 12.0, 1.0, crate::DType::I32)
653 .unwrap()
654 .reshape(&[4, 3])
655 .unwrap()
656 .t()
657 .unwrap(); let update = Array::from_slice(&[-1i32, -2, -3, -4, -5, -6, -7, -8], &[2, 4]).unwrap();
659 let primitive = Primitive::SliceUpdate {
660 start: vec![0, 3],
661 step: vec![2, -1],
662 len: vec![2, 4],
663 };
664 let out = Array::from_op(primitive.clone(), vec![base.clone(), update.clone()])
665 .unwrap()
666 .remove(0);
667 assert_eq!(
668 out.to_vec::<i32>().unwrap(),
669 [-4, -3, -2, -1, 1, 4, 7, 10, -8, -7, -6, -5]
670 );
671 let wrong = Array::from_slice(&[0i32; 3], &[3]).unwrap();
672 assert!(Array::from_op(primitive, vec![base, wrong]).is_err());
673 }
674
675 #[test]
676 fn tracer_nodes_keep_their_graph_only_while_tracing() {
677 let x = Array::scalar(2.0f32);
678 let kept = std::cell::Cell::new(false);
679 let g = crate::grad(|x: &Array| {
680 let y = x * x;
681 y.eval()?;
682 kept.set(matches!(&*y.node.lock(), State::Retained { .. }));
683 Ok(y)
684 });
685 assert_eq!(g(&x).unwrap().item::<f32>().unwrap(), 4.0);
686 assert!(kept.get(), "retained inside a transform");
687 let traced = crate::vjp(
688 |xs| Ok(vec![&xs[0] * &xs[0]]),
689 &[x],
690 &[Array::scalar(1.0f32)],
691 )
692 .unwrap()
693 .0
694 .remove(0);
695 traced.eval().unwrap();
696 assert!(
697 matches!(&*traced.node.lock(), State::Done),
698 "outside a transform"
699 );
700 }
701
702 #[test]
703 fn evaluation_detaches_inputs() {
704 let a = Array::scalar(1u8);
705 let c = &a + &a;
706 assert!(c.primitive().is_some());
707 c.eval().unwrap();
708 assert_eq!(c.primitive(), None);
709 assert_eq!(Arc::strong_count(&a.node), 1, "c no longer references a");
710 }
711
712 fn positions(arrays: &[Array]) -> NodeMap<*const Node, usize> {
714 let order = topological_order(arrays);
715 let at: NodeMap<*const Node, usize> = order
716 .iter()
717 .enumerate()
718 .map(|(i, n)| (Arc::as_ptr(n), i))
719 .collect();
720 assert_eq!(at.len(), order.len(), "each node once");
721 for (i, node) in order.iter().enumerate() {
722 if let State::Pending { inputs, .. } = &*node.lock() {
723 for input in inputs {
724 if let Some(&j) = at.get(&Arc::as_ptr(&input.node)) {
725 assert!(j < i, "inputs come first");
726 }
727 }
728 }
729 }
730 at
731 }
732
733 #[test]
734 fn independent_work_is_ordered_side_by_side() {
735 let x = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
736 let (a1, b1) = (&x + 1.0, &x + 3.0);
737 let (a2, b2) = (&a1 * 2.0, &b1 * 4.0);
738 let top = &a2 + &b2;
739 let at = positions(std::slice::from_ref(&top));
740 let p = |a: &Array| at[&Arc::as_ptr(&a.node)];
741 assert!(
742 p(&a1).max(p(&b1)) < p(&a2).min(p(&b2)),
743 "both chains advance together"
744 );
745 assert_eq!(top.to_vec::<f32>().unwrap(), [20.0, 26.0]);
746 }
747
748 #[test]
749 fn wide_and_shared_graphs_are_ordered() {
750 let x = Array::from_slice(&[1i32], &[1]).unwrap();
751 let one = Array::from_slice(&[1i32], &[1]).unwrap();
752 let shared = &x + &x;
753 let mut joined = shared.clone();
755 for i in 0..100 {
756 let mut c = &shared + &Array::from_slice(&[i], &[1]).unwrap();
757 for _ in 0..i % 7 {
758 c = &c * &one;
759 }
760 joined = &joined + &c;
761 }
762 positions(&[joined.clone(), shared.clone(), joined.clone()]);
763 counter::take();
764 eval(&[joined.clone(), shared.clone()]).unwrap();
765 let nodes = 1 + 100 * 2 + (0..100).map(|i| i % 7).sum::<usize>();
766 assert_eq!(counter::take(), nodes, "every node once");
767 assert_eq!(
768 joined.item::<i32>().unwrap(),
769 2 + 100 * 2 + (0..100).sum::<i32>()
770 );
771 }
772
773 #[test]
774 fn scheduled_evaluation_skips_work_done_meanwhile() {
775 let x = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
776 let shared = &x * &x;
777 let top = &shared + &x;
778 let scheduled = schedule(std::slice::from_ref(&top));
779 assert_eq!(scheduled.arrays().len(), 1);
780 counter::take();
781 shared.eval().unwrap();
782 scheduled.eval().unwrap();
783 assert_eq!(counter::take(), 2, "shared once, then top");
784 assert_eq!(top.to_vec::<f32>().unwrap(), [2.0, 6.0]);
785 }
786}