1use std::sync::Arc;
4
5use crate::array::{Completion, Data, Node, NodeMap, NodeSet, State};
6use crate::gpu::GpuEval;
7use crate::kernels::{self, Operand};
8use crate::primitive::Primitive;
9use crate::tree::{Leaves, collect_leaves};
10use crate::{Array, DeviceKind, Error, Result, view};
11
12pub fn eval<T: Leaves + ?Sized>(tree: &T) -> Result<()> {
32 schedule(&collect_leaves(tree)).eval()
33}
34
35pub fn async_eval<T: Leaves + ?Sized>(tree: &T) -> Result<()> {
57 schedule(&collect_leaves(tree)).async_eval()
58}
59
60pub fn schedule(arrays: &[Array]) -> Scheduled {
82 Scheduled {
83 arrays: arrays.to_vec(),
84 order: topological_order(arrays),
85 }
86}
87
88pub struct Scheduled {
90 arrays: Vec<Array>,
91 order: Vec<Arc<Node>>,
92}
93
94impl std::fmt::Debug for Scheduled {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.debug_struct("Scheduled")
97 .field("arrays", &self.arrays)
98 .field("nodes", &self.order.len())
99 .finish()
100 }
101}
102
103impl Scheduled {
104 pub fn arrays(&self) -> &[Array] {
106 &self.arrays
107 }
108
109 pub fn async_eval(self) -> Result<()> {
111 let arrays = self.arrays.clone();
112 self.submit().commit();
113 match arrays.iter().find_map(Array::slot_error) {
114 Some(err) => Err(err),
115 None => Ok(()),
116 }
117 }
118
119 pub fn eval(self) -> Result<()> {
121 let arrays = self.arrays.clone();
122 let _ = self.submit().flush();
124 wait(&arrays)
125 }
126
127 fn submit(self) -> GpuEval {
130 let mut gpu = GpuEval::new();
131 for node in self.order {
132 evaluate(&node, &mut gpu);
133 }
136 gpu
137 }
138}
139
140fn wait(arrays: &[Array]) -> Result<()> {
142 for array in arrays {
143 if let Err(err) = array.out().slot.wait() {
144 return Err(err.clone());
145 }
146 }
147 Ok(())
148}
149
150const BFS_MAX_WIDTH: usize = 20;
152
153fn topological_order(arrays: &[Array]) -> Vec<Arc<Node>> {
162 let mut inputs: NodeMap<*const Node, Vec<Arc<Node>>> = NodeMap::default();
164 let mut done = NodeSet::default();
165 let mut stack: Vec<Arc<Node>> = arrays.iter().map(|a| a.node.clone()).collect();
166 while let Some(node) = stack.pop() {
167 let key = Arc::as_ptr(&node);
168 if inputs.contains_key(&key) || done.contains(&key) {
169 continue;
170 }
171 let pending: Vec<Arc<Node>> = match &*node.lock() {
172 State::Pending { inputs, .. } => inputs.iter().map(|a| a.node.clone()).collect(),
173 State::Running { .. } | State::Retained { .. } | State::Done | State::Failed { .. } => {
175 done.insert(key);
176 continue;
177 }
178 };
179 stack.extend(pending.iter().cloned());
180 inputs.insert(key, pending);
181 }
182 for list in inputs.values_mut() {
183 list.retain(|n| !done.contains(&Arc::as_ptr(n)));
184 }
185 let roots: Vec<Arc<Node>> = arrays
186 .iter()
187 .map(|a| a.node.clone())
188 .filter(|n| inputs.contains_key(&Arc::as_ptr(n)))
189 .collect();
190 let mut consumers: NodeMap<*const Node, usize> = NodeMap::default();
193 for n in inputs.values().flatten().chain(&roots) {
194 *consumers.entry(Arc::as_ptr(n)).or_default() += 1;
195 }
196 let mut tape: Vec<Option<Arc<Node>>> = vec![None];
198 let mut deferred: Vec<(Option<Arc<Node>>, usize)> = Vec::new();
199 let mut next = 0;
200 while !consumers.is_empty() && (next < tape.len() || !deferred.is_empty()) {
201 let (node, mut j) = if next < tape.len() {
202 next += 1;
203 (tape[next - 1].clone(), 0)
204 } else {
205 deferred.pop().expect("checked above")
206 };
207 let list = match &node {
208 Some(n) => &inputs[&Arc::as_ptr(n)],
209 None => &roots,
210 };
211 while j < list.len() {
212 let key = Arc::as_ptr(&list[j]);
213 if consumers.contains_key(&key) {
214 if tape.len() - next >= BFS_MAX_WIDTH {
215 deferred.push((node.clone(), j));
216 break;
217 }
218 let left = consumers.get_mut(&key).expect("checked above");
219 *left -= 1;
220 if *left == 0 {
221 consumers.remove(&key);
222 tape.push(Some(list[j].clone()));
223 }
224 }
225 j += 1;
226 }
227 }
228 tape.into_iter().rev().flatten().collect()
229}
230
231fn evaluate(node: &Arc<Node>, gpu: &mut GpuEval) {
240 let done = Arc::new(Completion::default());
241 let (primitive, mut inputs) = {
242 let mut state = node.lock();
243 if !matches!(*state, State::Pending { .. }) {
244 return; }
246 let running = State::Running {
247 owner: std::thread::current().id(),
248 done: done.clone(),
249 };
250 match std::mem::replace(&mut *state, running) {
251 State::Pending { primitive, inputs } => (primitive, inputs),
252 _ => unreachable!("checked above"),
253 }
254 };
255 let running = Running {
256 node,
257 done,
258 finished: false,
259 };
260 #[cfg(test)]
261 counter::inc();
262 let result = match donation(node, &primitive, &mut inputs, &mut *gpu) {
263 Some(base) => in_place(node, &primitive, &inputs, base, gpu),
264 None => compute(node, &primitive, &inputs, gpu),
265 };
266 let failure = match result {
267 Ok((outputs, true)) => {
268 gpu.defer(node.clone(), outputs);
269 None
270 }
271 Ok((outputs, false)) => {
272 for (output, data) in node.outputs.iter().zip(outputs) {
273 let _ = output.slot.set(Ok(data));
274 }
275 None
276 }
277 Err(err) => {
278 for output in &node.outputs {
279 let _ = output.slot.set(Err(err.clone()));
280 }
281 Some(err)
282 }
283 };
284 if crate::transforms::retain(node) {
286 running.finish(State::Retained { primitive, inputs });
287 return;
288 }
289 running.finish(match failure {
290 Some(error) => State::Failed { error },
291 None => State::Done,
292 });
293 drop(inputs); }
295
296struct Running<'a> {
300 node: &'a Arc<Node>,
301 done: Arc<Completion>,
302 finished: bool,
303}
304
305impl Running<'_> {
306 fn finish(mut self, state: State) {
307 *self.node.lock() = state;
308 self.done.finish();
309 self.finished = true;
311 }
312}
313
314impl Drop for Running<'_> {
315 fn drop(&mut self) {
316 if self.finished {
317 return;
318 }
319 let error = Error::Backend("the evaluation of this array panicked".into());
320 for output in &self.node.outputs {
321 let _ = output.slot.set(Err(error.clone()));
322 }
323 *self.node.lock() = State::Failed { error };
324 self.done.finish();
325 }
326}
327
328fn wait_running(node: &Node, gpu: &mut GpuEval) -> Result<()> {
332 let done = match &*node.lock() {
333 State::Running { owner, done } => {
334 if *owner == std::thread::current().id() {
335 return Err(Error::InvalidArgument(
336 "eval: an array was evaluated from within its own evaluation".into(),
337 ));
338 }
339 done.clone()
340 }
341 State::Failed { error } => return Err(error.clone()),
342 _ => return Ok(()),
343 };
344 gpu.commit();
345 done.wait();
346 match &*node.lock() {
347 State::Failed { error } => Err(error.clone()),
348 _ => Ok(()),
349 }
350}
351
352fn donation(
357 node: &Node,
358 primitive: &Primitive,
359 inputs: &mut [Array],
360 gpu: &mut GpuEval,
361) -> Option<(usize, Data)> {
362 let device = node.stream.device;
363 if node.is_tracer() {
364 return None;
365 }
366 if device.kind == DeviceKind::Cpu {
367 return match primitive {
369 Primitive::SliceUpdate { .. } if donatable_size(inputs[0].size()) => {
370 owned_on_cpu(&mut inputs[0]).map(|data| (0, data))
371 }
372 _ => None,
373 };
374 }
375 if !gpu.available(device) {
376 return None;
377 }
378 let base = match primitive {
379 Primitive::SliceUpdate { .. }
380 | Primitive::Fast(crate::fast::imp::FastOp::NormRope { cache: true, .. })
381 | Primitive::Fast(crate::fast::imp::FastOp::PagedWrite) => Some(0),
382 Primitive::Fast(crate::fast::imp::FastOp::GatedDeltaSlots) => Some(5),
383 _ => None,
384 };
385 if let Some(at) = base {
386 if !donatable_size(inputs[at].size()) {
387 return None;
388 }
389 return owned(&mut inputs[at], gpu).map(|data| (at, data));
390 }
391 let [out] = node.outputs.as_slice() else {
392 return None;
393 };
394 for i in in_place_inputs(primitive, inputs, out) {
395 if let Some(data) = owned(&mut inputs[i], gpu) {
396 return Some((i, data));
397 }
398 }
399 None
400}
401
402fn donatable_size(size: usize) -> bool {
405 size > 0 && size < u32::MAX as usize
406}
407
408fn in_place_inputs(
411 primitive: &Primitive,
412 inputs: &[Array],
413 out: &crate::array::Output,
414) -> Vec<usize> {
415 use crate::{DType, UnaryOp};
416 let candidates: &[usize] = match primitive {
417 Primitive::Unary(op) if *op != UnaryOp::Conjugate => &[0],
418 Primitive::Binary(_) => &[0, 1],
419 Primitive::Select => &[1, 2],
420 Primitive::Fast(
421 crate::fast::imp::FastOp::Softmax | crate::fast::imp::FastOp::CausalSoftmax { .. },
422 ) => &[0],
423 _ => &[],
424 };
425 let float = matches!(out.dtype, DType::F16 | DType::BF16 | DType::F32);
426 if !float || out.size == 0 {
427 return Vec::new();
428 }
429 if out.size >= u32::MAX as usize {
431 let large = match primitive {
432 Primitive::Fast(_) => {
433 let axis = out.shape.last().copied().unwrap_or(1).max(1);
434 out.size / axis * 1024 < u32::MAX as usize
435 }
436 _ => {
437 let metas: Vec<&crate::array::Output> = inputs.iter().map(Array::out).collect();
438 crate::gpu::large_elementwise(&metas, &out.shape)
439 }
440 };
441 if !large {
442 return Vec::new();
443 }
444 }
445 candidates
446 .iter()
447 .copied()
448 .filter(|&i| inputs[i].dtype() == out.dtype && inputs[i].dims() == out.shape.as_slice())
449 .collect()
450}
451
452fn owned(input: &mut Array, gpu: &mut GpuEval) -> Option<Data> {
472 let at = input.output as usize;
475 let base = &mut input.node;
476 let usable = |data: &Data, out: &crate::array::Output, handles: usize| {
477 out.flags.row_contiguous
478 && crate::gpu::writable(data, out)
479 && Arc::strong_count(&data.buffer) == handles
480 };
481 if let Some(out) = base.outputs.get(at)
483 && let Some(data) = gpu.take_pending(base, at, |data, handles| usable(data, out, handles))
484 {
485 return Some(data);
486 }
487 let cell = base.outputs.get(at)?.inflight.clone();
491 let mut state = cell.lock();
492 if Arc::strong_count(base) != 1 {
493 return None;
494 }
495 if let Some(base) = Arc::get_mut(base) {
497 let out = base.outputs.get_mut(at)?;
498 if let Some(Ok(data)) = out.slot.get() {
499 let published = matches!(*state, crate::array::InFlightState::Set(_));
501 if !usable(data, out, 1 + usize::from(published)) {
502 return None;
503 }
504 if published {
505 *state = crate::array::InFlightState::Unset;
506 }
507 return match out.slot.take() {
508 Some(Ok(data)) => Some(data),
509 _ => None,
510 };
511 }
512 }
513 let out = base.outputs.get(at)?;
515 if out.slot.get().is_some() {
516 return None;
517 }
518 let crate::array::InFlightState::Set(data) = &*state else {
519 return None;
520 };
521 if !usable(data, out, 1) {
522 return None;
523 }
524 match std::mem::replace(&mut *state, crate::array::InFlightState::Donated) {
525 crate::array::InFlightState::Set(data) => Some(data),
526 _ => None,
527 }
528}
529
530fn owned_on_cpu(input: &mut Array) -> Option<Data> {
534 let at = input.output as usize;
535 let base = Arc::get_mut(&mut input.node)?;
536 let out = base.outputs.get_mut(at)?;
537 if out.inflight.get().is_some() {
538 return None;
539 }
540 let Some(Ok(data)) = out.slot.get() else {
541 return None;
542 };
543 let whole = data.offset == 0 && data.buffer.len() == out.size * out.dtype.size();
544 if !(out.flags.row_contiguous
545 && whole
546 && Arc::strong_count(&data.buffer) == 1
547 && data.buffer.cpu_writable())
548 {
549 return None;
550 }
551 match out.slot.take() {
552 Some(Ok(data)) => Some(data),
553 _ => None,
554 }
555}
556
557fn in_place(
560 node: &Node,
561 primitive: &Primitive,
562 inputs: &[Array],
563 (at, base): (usize, Data),
564 gpu: &mut GpuEval,
565) -> Result<(Vec<Data>, bool)> {
566 let (mut pending, mut foreign) = (false, Vec::new());
567 let mut rest = Vec::with_capacity(inputs.len());
568 for (i, a) in inputs.iter().enumerate() {
569 rest.push(match i == at {
570 true => None,
571 false => Some(input_data(a, gpu, &mut pending, &mut foreign)?),
572 });
573 }
574 let out = &node.outputs[0];
575 let operands = |skip: usize| -> Vec<Operand<'_>> {
576 inputs
577 .iter()
578 .zip(&rest)
579 .skip(skip)
580 .filter_map(|(a, d)| d.as_ref().map(|d| (a.out(), d)))
581 .collect()
582 };
583 if let Primitive::Fast(crate::fast::imp::FastOp::GatedDeltaSlots) = primitive {
584 let pool = inputs[at].out();
585 let all = operands(0);
586 let datas = gpu.gated_delta_slots_in_place(&all, pool, base, &node.outputs)?;
587 return Ok((datas, true));
588 }
589 let data = match primitive {
590 Primitive::Fast(crate::fast::imp::FastOp::PagedWrite) => {
591 gpu.paged_write_in_place(base, &operands(1), out)?
592 }
593 Primitive::Fast(crate::fast::imp::FastOp::Softmax) => {
594 gpu.softmax_in_place(base, out, None)?
595 }
596 Primitive::Fast(crate::fast::imp::FastOp::CausalSoftmax { rows }) => {
597 gpu.softmax_in_place(base, out, Some(*rows as usize))?
598 }
599 Primitive::Fast(op) => gpu.norm_rope_in_place(base, op, &operands(1), out)?,
600 Primitive::SliceUpdate { .. } if node.stream.device.kind == DeviceKind::Cpu => {
601 for input in &foreign {
603 input.out().slot.wait().clone()?;
604 }
605 if pending {
606 gpu.flush()?;
607 }
608 let slices = primitive.dim_slices(out.shape.len())?;
609 let mut base = base;
610 let buffer = Arc::get_mut(&mut base.buffer).expect("donated: the only handle");
611 crate::kernels::slice_update_into(buffer, &out.shape, operands(1)[0], &slices);
612 return Ok((vec![base], false));
613 }
614 Primitive::SliceUpdate { .. } => {
615 let slices = primitive.dim_slices(out.shape.len())?;
616 gpu.slice_update_in_place(base, out, operands(1)[0], &slices)?
617 }
618 _ => {
619 let metas: Vec<&crate::array::Output> = inputs.iter().map(Array::out).collect();
620 let datas: Vec<Option<&Data>> = rest.iter().map(Option::as_ref).collect();
621 gpu.elementwise_in_place(primitive, &metas, &datas, base, out)?
622 }
623 };
624 Ok((vec![data], true))
625}
626
627fn input_data<'a>(
630 input: &'a Array,
631 gpu: &mut GpuEval,
632 pending: &mut bool,
633 foreign: &mut Vec<&'a Array>,
634) -> Result<Data> {
635 if let Some(d) = gpu.lookup(&input.node, input.output) {
636 *pending = true;
637 Ok(d)
638 } else if let Some(result) = input.out().slot.get() {
639 result.clone()
640 } else if let Some(d) = input.out().inflight.get() {
641 *pending = true;
644 foreign.push(input);
645 gpu.read_in_flight(input);
646 Ok(d)
647 } else {
648 gpu.commit();
652 wait_running(&input.node, gpu)?;
653 if let Some(result) = input.out().slot.get() {
654 return result.clone();
655 }
656 if let Some(d) = input.out().inflight.get() {
657 *pending = true;
658 foreign.push(input);
659 gpu.read_in_flight(input);
660 return Ok(d);
661 }
662 input.out().slot.wait().clone()
665 }
666}
667
668fn compute(
671 node: &Node,
672 primitive: &Primitive,
673 inputs: &[Array],
674 gpu: &mut GpuEval,
675) -> Result<(Vec<Data>, bool)> {
676 let mut data = Vec::with_capacity(inputs.len());
677 let mut pending = false;
678 let mut foreign = Vec::new();
680 for input in inputs {
681 data.push(input_data(input, gpu, &mut pending, &mut foreign)?);
682 }
683 let operands: Vec<Operand<'_>> = inputs
684 .iter()
685 .zip(&data)
686 .map(|(a, d)| (a.out(), d))
687 .collect();
688 let await_foreign = || -> Result<()> {
691 for input in &foreign {
692 input.out().slot.wait().clone()?;
693 }
694 Ok(())
695 };
696 if matches!(primitive, Primitive::Depends { .. }) {
697 gpu.release_unreferenced();
701 }
702 if let Some(views) = view_outputs(primitive, &operands)? {
703 gpu.depend_on(&foreign);
706 return Ok((views, pending));
707 }
708 let device = node.stream.device;
709 if device.kind != DeviceKind::Cpu {
710 if !gpu.available(device) {
711 return Err(Error::UnsupportedDevice(device));
712 }
713 if gpu.supports(primitive, &operands, &node.outputs) {
714 return Ok((gpu.run(primitive, &operands, &node.outputs)?, true));
715 }
716 }
717 await_foreign()?;
719 if pending {
720 gpu.flush()?;
721 }
722 Ok((cpu(primitive, &operands)?, false))
723}
724
725fn view_outputs(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Option<Vec<Data>>> {
728 let share = |offset| {
729 Some(vec![Data {
730 buffer: operands[0].1.buffer.clone(),
731 offset,
732 }])
733 };
734 Ok(match primitive {
735 Primitive::Transpose(_)
736 | Primitive::Broadcast(_)
737 | Primitive::StopGradient
738 | Primitive::Depends { .. } => share(operands[0].1.offset),
739 Primitive::Slice { .. } => {
740 let (meta, data) = operands[0];
741 let (_, delta) =
742 view::slice_geometry(&meta.strides, &primitive.dim_slices(meta.shape.len())?);
743 share(data.offset + delta)
744 }
745 Primitive::Custom(call) => Some(
746 operands[call.primals..]
747 .iter()
748 .map(|(_, data)| (*data).clone())
749 .collect(),
750 ),
751 Primitive::Reshape(shape) => {
752 let (meta, data) = operands[0];
753 view::nocopy_reshape(&meta.shape, &meta.strides, shape).and_then(|_| share(data.offset))
754 }
755 Primitive::Squeeze(_) | Primitive::ExpandDims(_) => share(operands[0].1.offset),
757 Primitive::View(dtype) => {
759 let (meta, data) = operands[0];
760 let (from, to) = (meta.dtype.size() as isize, dtype.size() as isize);
761 let bytes = data.offset * from;
762 let shared = from == to || (meta.flags.row_contiguous && bytes % to == 0);
763 shared.then(|| share(bytes / to)).flatten()
764 }
765 _ => None,
766 })
767}
768
769fn cpu(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Vec<Data>> {
771 let new = |buffer| Ok(vec![Data::new(buffer)]);
772 match primitive {
773 Primitive::Unary(op) => new(kernels::unary(*op, operands[0])?),
774 Primitive::Binary(op) => new(kernels::binary(*op, operands[0], operands[1])?),
775 Primitive::Select => new(kernels::select(operands[0], operands[1], operands[2])?),
776 Primitive::AsType(dtype) => new(kernels::cast(operands[0], *dtype)?),
777 Primitive::ToFp8 => new(kernels::to_fp8(operands[0])?),
778 Primitive::SortedOffsets { groups } => new(kernels::sorted_offsets(operands[0], *groups)?),
779 Primitive::FromFp8(dtype) => new(kernels::from_fp8(operands[0], *dtype)?),
780 Primitive::Reduce { op, axes } => new(kernels::reduce(*op, operands[0], axes)?),
781 Primitive::ArgReduce { op, axis } => new(kernels::arg_reduce(*op, operands[0], *axis)?),
782 Primitive::GatherAxis { axis } => {
783 new(kernels::gather_axis(operands[0], operands[1], *axis)?)
784 }
785 Primitive::ScatterAxis { axis, reduce } => new(kernels::scatter_axis(
786 operands[0],
787 operands[1],
788 operands[2],
789 *axis,
790 *reduce,
791 )?),
792 Primitive::Concatenate { axis } => {
793 let mut shape = operands[0].0.shape.clone();
794 shape[*axis] = operands.iter().map(|(m, _)| m.shape[*axis]).sum();
795 new(kernels::concatenate(operands, &shape, *axis)?)
796 }
797 Primitive::Sort { axis } => new(kernels::sort(operands[0], *axis, false)?),
798 Primitive::ArgSort { axis } => new(kernels::sort(operands[0], *axis, true)?),
799 Primitive::Scan {
800 op,
801 axis,
802 reverse,
803 inclusive,
804 } => new(kernels::scan(
805 *op,
806 operands[0],
807 *axis,
808 *reverse,
809 *inclusive,
810 )?),
811 Primitive::Matmul => new(kernels::matmul(operands[0], operands[1])?),
812 Primitive::AddMM { .. } => {
813 let (alpha, beta) = primitive.addmm_factors();
814 new(kernels::addmm(
815 operands[0],
816 operands[1],
817 operands[2],
818 alpha,
819 beta,
820 )?)
821 }
822 Primitive::RandomBits { shape, .. } => {
823 let n: usize = shape.iter().product();
824 new(kernels::random_bits(
825 operands[0],
826 n * primitive_width(primitive),
827 )?)
828 }
829 Primitive::Fast(op) => op.eval_cpu(operands),
830 Primitive::MetalKernel(_) => Err(Error::InvalidArgument(
831 "metal_kernel: custom kernels run on the GPU only".into(),
832 )),
833 Primitive::Compiled(fused) => fused.eval_cpu(operands),
834 Primitive::Collective { op, group } => crate::distributed::eval_cpu(op, *group, operands),
835 Primitive::Quantize { quant } => {
836 let (group_size, bits) = quant.checked("quantize")?;
837 if quant.mode != crate::QuantMode::Affine {
838 let (packed, scales) = kernels::fp_quantize(operands[0], group_size, bits)?;
839 return Ok(vec![Data::new(packed), Data::new(scales)]);
840 }
841 let (packed, scales, biases) = kernels::quantize(operands[0], group_size, bits)?;
842 Ok(vec![
843 Data::new(packed),
844 Data::new(scales),
845 Data::new(biases),
846 ])
847 }
848 Primitive::Dequantize { quant, dtype } => {
849 let (group_size, bits) = quant.checked("dequantize")?;
850 new(match quant.mode {
851 crate::QuantMode::Affine => {
852 kernels::dequantize(operands[0], operands[1], operands[2], group_size, bits)?
853 }
854 _ => kernels::fp_dequantize(operands[0], operands[1], group_size, bits, *dtype)?,
855 })
856 }
857 Primitive::QuantizedMatmul { quant, transpose } => {
858 let (group_size, bits) = quant.checked("quantized_matmul")?;
859 new(kernels::quantized_matmul(
860 operands, *quant, group_size, bits, *transpose,
861 )?)
862 }
863 Primitive::SliceUpdate { .. } => new(kernels::slice_update(
864 operands[0],
865 operands[1],
866 &primitive.dim_slices(operands[0].0.shape.len())?,
867 )?),
868 Primitive::Reshape(_)
871 | Primitive::Squeeze(_)
872 | Primitive::ExpandDims(_)
873 | Primitive::Copy
874 | Primitive::View(_) => new(kernels::contiguous(operands[0].0, operands[0].1)?),
875 Primitive::Transpose(_)
876 | Primitive::Broadcast(_)
877 | Primitive::Slice { .. }
878 | Primitive::StopGradient
879 | Primitive::Depends { .. }
880 | Primitive::Custom(_) => {
881 unreachable!("views are handled by view_outputs")
882 }
883 #[cfg(test)]
884 Primitive::AddMultiply => {
885 use crate::primitive::BinaryOp;
886 Ok(vec![
887 Data::new(kernels::binary(BinaryOp::Add, operands[0], operands[1])?),
888 Data::new(kernels::binary(
889 BinaryOp::Multiply,
890 operands[0],
891 operands[1],
892 )?),
893 ])
894 }
895 }
896}
897
898fn primitive_width(primitive: &Primitive) -> usize {
899 match primitive {
900 Primitive::RandomBits { width, .. } => usize::from(*width),
901 _ => unreachable!(),
902 }
903}
904
905#[cfg(test)]
907pub(crate) mod counter {
908 use std::cell::Cell;
909
910 thread_local!(static EVALUATED: Cell<usize> = const { Cell::new(0) });
911
912 pub(crate) fn inc() {
913 EVALUATED.set(EVALUATED.get() + 1);
914 }
915 pub(crate) fn take() -> usize {
916 EVALUATED.replace(0)
917 }
918}
919
920#[cfg(test)]
921mod tests {
922 use super::*;
923 use crate::buffer::live;
924
925 #[test]
926 fn shared_node_runs_once() {
927 let x = Array::from_slice(&[1.0f32, 2.0], [2]);
928 let shared = &x + &x;
929 let left = &shared * &x;
930 let right = &shared + &x;
931 let top = &left + &right;
932 counter::take();
933 eval(&[top.clone(), left.clone()]).unwrap();
934 assert_eq!(counter::take(), 4, "shared, left, right, top: once each");
935 top.eval().unwrap();
936 assert_eq!(counter::take(), 0, "already evaluated");
937 assert_eq!(top.to_vec::<f32>().unwrap(), vec![5.0, 14.0]);
938 }
939
940 #[test]
941 fn running_releases_its_completion() {
942 let x = Array::from_slice(&[1.0f32], [1]);
945 for finish in [true, false] {
946 let done = Arc::new(Completion::default());
947 let running = Running {
948 node: &x.node,
949 done: done.clone(),
950 finished: false,
951 };
952 if finish {
953 running.finish(State::Done);
954 } else {
955 drop(running);
956 }
957 assert_eq!(Arc::strong_count(&done), 1, "finish = {finish}");
958 }
959 }
960
961 #[test]
962 fn intermediates_freed() {
963 crate::set_default_stream(crate::Stream::default_for(crate::Device::cpu()));
966 let before = live::count();
967 let mut x = Array::from(0i64);
968 let one = Array::from(1i64);
969 let steps = if cfg!(miri) { 100 } else { 10_000 };
970 for _ in 0..steps {
971 x = &x + &one;
972 }
973 x.eval().unwrap();
974 drop(one);
975 assert_eq!(
976 live::count() - before,
977 1,
978 "only the result's buffer is alive"
979 );
980 assert_eq!(x.item::<i64>().unwrap(), steps as i64);
981 }
982
983 #[test]
984 fn multi_output_once() {
985 let a = Array::from_slice(&[2i32, 3], [2]);
986 let b = Array::from_slice(&[5i32, 7], [2]);
987 let mut outs = Array::from_op(Primitive::AddMultiply, vec![a, b]).unwrap();
988 let product = outs.pop().unwrap();
989 let sum = outs.pop().unwrap();
990 counter::take();
991 eval(&[sum.clone(), product.clone()]).unwrap();
992 assert_eq!(counter::take(), 1);
993 drop(sum);
994 assert_eq!(product.to_vec::<i32>().unwrap(), vec![10, 21]);
995 }
996
997 #[test]
998 fn slice_update_writes_a_strided_region() {
999 let base = crate::ops::imp::arange(0.0, 12.0, 1.0, crate::DType::I32)
1001 .unwrap()
1002 .try_reshape(&[4, 3])
1003 .unwrap()
1004 .t(); let update = Array::from_slice(&[-1i32, -2, -3, -4, -5, -6, -7, -8], [2, 4]);
1006 let primitive = Primitive::SliceUpdate {
1007 start: vec![0, 3],
1008 step: vec![2, -1],
1009 len: vec![2, 4],
1010 };
1011 let out = Array::from_op(primitive.clone(), vec![base.clone(), update.clone()])
1012 .unwrap()
1013 .remove(0);
1014 assert_eq!(
1015 out.to_vec::<i32>().unwrap(),
1016 [-4, -3, -2, -1, 1, 4, 7, 10, -8, -7, -6, -5]
1017 );
1018 let wrong = Array::from_slice(&[0i32; 3], [3]);
1019 assert!(Array::from_op(primitive, vec![base, wrong]).is_err());
1020 }
1021
1022 #[test]
1023 fn tracer_nodes_keep_their_graph_only_while_tracing() {
1024 let x = Array::from(2.0f32);
1025 let kept = std::cell::Cell::new(false);
1026 let g = crate::grad(|x: &Array| {
1027 let y = x * x;
1028 y.eval()?;
1029 kept.set(matches!(&*y.node.lock(), State::Retained { .. }));
1030 Ok(y)
1031 });
1032 assert_eq!(g(&x).unwrap().item::<f32>().unwrap(), 4.0);
1033 assert!(kept.get(), "retained inside a transform");
1034 let traced = crate::vjp_flat(|xs| Ok(vec![&xs[0] * &xs[0]]), &[x], &[Array::from(1.0f32)])
1035 .unwrap()
1036 .0
1037 .remove(0);
1038 traced.eval().unwrap();
1039 assert!(
1040 matches!(&*traced.node.lock(), State::Done),
1041 "outside a transform"
1042 );
1043 }
1044
1045 #[test]
1046 fn huge_buffers_are_never_donated() {
1047 assert!(donatable_size(1));
1049 assert!(donatable_size(u32::MAX as usize - 1));
1050 assert!(!donatable_size(u32::MAX as usize));
1051 assert!(!donatable_size(1 << 33));
1052 assert!(!donatable_size(0));
1053 let base =
1056 crate::ops::imp::broadcast_to(&Array::from(0.0f32), &[1 << 16, 1 << 16]).unwrap();
1057 let update = Array::from_slice(&[1.0f32], [1, 1]);
1058 let primitive = Primitive::SliceUpdate {
1059 start: vec![0, 0],
1060 step: vec![1, 1],
1061 len: vec![1, 1],
1062 };
1063 let out = Array::from_op(primitive.clone(), vec![base.clone(), update.clone()])
1064 .unwrap()
1065 .remove(0);
1066 let mut inputs = vec![base, update];
1067 let mut gpu = GpuEval::new();
1068 assert!(donation(&out.node, &primitive, &mut inputs, &mut gpu).is_none());
1069 }
1070
1071 #[test]
1072 fn evaluation_detaches_inputs() {
1073 let a = Array::from(1u8);
1074 let c = &a + &a;
1075 assert!(c.primitive_t().is_some());
1076 c.eval().unwrap();
1077 assert_eq!(c.primitive_t(), None);
1078 assert_eq!(Arc::strong_count(&a.node), 1, "c no longer references a");
1079 }
1080
1081 fn positions(arrays: &[Array]) -> NodeMap<*const Node, usize> {
1083 let order = topological_order(arrays);
1084 let at: NodeMap<*const Node, usize> = order
1085 .iter()
1086 .enumerate()
1087 .map(|(i, n)| (Arc::as_ptr(n), i))
1088 .collect();
1089 assert_eq!(at.len(), order.len(), "each node once");
1090 for (i, node) in order.iter().enumerate() {
1091 if let State::Pending { inputs, .. } = &*node.lock() {
1092 for input in inputs {
1093 if let Some(&j) = at.get(&Arc::as_ptr(&input.node)) {
1094 assert!(j < i, "inputs come first");
1095 }
1096 }
1097 }
1098 }
1099 at
1100 }
1101
1102 #[test]
1103 fn independent_work_is_ordered_side_by_side() {
1104 let x = Array::from_slice(&[1.0f32, 2.0], [2]);
1105 let (a1, b1) = (&x + 1.0, &x + 3.0);
1106 let (a2, b2) = (&a1 * 2.0, &b1 * 4.0);
1107 let top = &a2 + &b2;
1108 let at = positions(std::slice::from_ref(&top));
1109 let p = |a: &Array| at[&Arc::as_ptr(&a.node)];
1110 assert!(
1111 p(&a1).max(p(&b1)) < p(&a2).min(p(&b2)),
1112 "both chains advance together"
1113 );
1114 assert_eq!(top.to_vec::<f32>().unwrap(), [20.0, 26.0]);
1115 }
1116
1117 #[test]
1118 fn wide_and_shared_graphs_are_ordered() {
1119 let x = Array::from_slice(&[1i32], [1]);
1120 let one = Array::from_slice(&[1i32], [1]);
1121 let shared = &x + &x;
1122 let mut joined = shared.clone();
1124 for i in 0..100 {
1125 let mut c = &shared + &Array::from_slice(&[i], [1]);
1126 for _ in 0..i % 7 {
1127 c = &c * &one;
1128 }
1129 joined = &joined + &c;
1130 }
1131 positions(&[joined.clone(), shared.clone(), joined.clone()]);
1132 counter::take();
1133 eval(&[joined.clone(), shared.clone()]).unwrap();
1134 let nodes = 1 + 100 * 2 + (0..100).map(|i| i % 7).sum::<usize>();
1135 assert_eq!(counter::take(), nodes, "every node once");
1136 assert_eq!(
1137 joined.item::<i32>().unwrap(),
1138 2 + 100 * 2 + (0..100).sum::<i32>()
1139 );
1140 }
1141
1142 #[test]
1143 fn scheduled_evaluation_skips_work_done_meanwhile() {
1144 let x = Array::from_slice(&[1.0f32, 2.0], [2]);
1145 let shared = &x * &x;
1146 let top = &shared + &x;
1147 let scheduled = schedule(std::slice::from_ref(&top));
1148 assert_eq!(scheduled.arrays().len(), 1);
1149 counter::take();
1150 shared.eval().unwrap();
1151 scheduled.eval().unwrap();
1152 assert_eq!(counter::take(), 2, "shared once, then top");
1153 assert_eq!(top.to_vec::<f32>().unwrap(), [2.0, 6.0]);
1154 }
1155}