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() || device.kind == DeviceKind::Cpu || !gpu.available(device) {
364 return None;
365 }
366 let base = match primitive {
367 Primitive::SliceUpdate { .. }
368 | Primitive::Fast(crate::fast::imp::FastOp::NormRope { cache: true, .. })
369 | Primitive::Fast(crate::fast::imp::FastOp::PagedWrite) => Some(0),
370 Primitive::Fast(crate::fast::imp::FastOp::GatedDeltaSlots) => Some(5),
371 _ => None,
372 };
373 if let Some(at) = base {
374 if !donatable_size(inputs[at].size()) {
375 return None;
376 }
377 return owned(&mut inputs[at], gpu).map(|data| (at, data));
378 }
379 let [out] = node.outputs.as_slice() else {
380 return None;
381 };
382 for i in in_place_inputs(primitive, inputs, out) {
383 if let Some(data) = owned(&mut inputs[i], gpu) {
384 return Some((i, data));
385 }
386 }
387 None
388}
389
390fn donatable_size(size: usize) -> bool {
393 size > 0 && size < u32::MAX as usize
394}
395
396fn in_place_inputs(
399 primitive: &Primitive,
400 inputs: &[Array],
401 out: &crate::array::Output,
402) -> Vec<usize> {
403 use crate::{DType, UnaryOp};
404 let candidates: &[usize] = match primitive {
405 Primitive::Unary(op) if *op != UnaryOp::Conjugate => &[0],
406 Primitive::Binary(_) => &[0, 1],
407 Primitive::Select => &[1, 2],
408 Primitive::Fast(crate::fast::imp::FastOp::Softmax) => &[0],
409 _ => &[],
410 };
411 let float = matches!(out.dtype, DType::F16 | DType::BF16 | DType::F32);
412 if !float || !donatable_size(out.size) {
413 return Vec::new();
414 }
415 candidates
416 .iter()
417 .copied()
418 .filter(|&i| inputs[i].dtype() == out.dtype && inputs[i].dims() == out.shape.as_slice())
419 .collect()
420}
421
422fn owned(input: &mut Array, gpu: &mut GpuEval) -> Option<Data> {
441 let at = input.output as usize;
444 let base = &mut input.node;
445 let usable = |data: &Data, out: &crate::array::Output, handles: usize| {
446 out.flags.row_contiguous
447 && crate::gpu::writable(data, out)
448 && Arc::strong_count(&data.buffer) == handles
449 };
450 if let Some(out) = base.outputs.get(at)
452 && let Some(data) = gpu.take_pending(base, at, |data, handles| usable(data, out, handles))
453 {
454 return Some(data);
455 }
456 if Arc::strong_count(base) != 1 {
457 return None;
458 }
459 if let Some(base) = Arc::get_mut(base) {
461 let out = base.outputs.get_mut(at)?;
462 if let Some(Ok(data)) = out.slot.get() {
463 let handles = 1 + usize::from(out.inflight.get().is_some());
465 if !usable(data, out, handles) {
466 return None;
467 }
468 out.inflight.clear();
469 return match out.slot.take() {
470 Some(Ok(data)) => Some(data),
471 _ => None,
472 };
473 }
474 }
475 let out = base.outputs.get(at)?;
477 if out.slot.get().is_some() {
478 return None;
479 }
480 let mut state = out.inflight.lock();
481 let crate::array::InFlightState::Set(data) = &*state else {
482 return None;
483 };
484 if !usable(data, out, 1) {
485 return None;
486 }
487 match std::mem::replace(&mut *state, crate::array::InFlightState::Donated) {
488 crate::array::InFlightState::Set(data) => Some(data),
489 _ => None,
490 }
491}
492
493fn in_place(
496 node: &Node,
497 primitive: &Primitive,
498 inputs: &[Array],
499 (at, base): (usize, Data),
500 gpu: &mut GpuEval,
501) -> Result<(Vec<Data>, bool)> {
502 let (mut pending, mut foreign) = (false, Vec::new());
503 let mut rest = Vec::with_capacity(inputs.len());
504 for (i, a) in inputs.iter().enumerate() {
505 rest.push(match i == at {
506 true => None,
507 false => Some(input_data(a, gpu, &mut pending, &mut foreign)?),
508 });
509 }
510 let out = &node.outputs[0];
511 let operands = |skip: usize| -> Vec<Operand<'_>> {
512 inputs
513 .iter()
514 .zip(&rest)
515 .skip(skip)
516 .filter_map(|(a, d)| d.as_ref().map(|d| (a.out(), d)))
517 .collect()
518 };
519 if let Primitive::Fast(crate::fast::imp::FastOp::GatedDeltaSlots) = primitive {
520 let pool = inputs[at].out();
521 let all = operands(0);
522 let datas = gpu.gated_delta_slots_in_place(&all, pool, base, &node.outputs)?;
523 return Ok((datas, true));
524 }
525 let data = match primitive {
526 Primitive::Fast(crate::fast::imp::FastOp::PagedWrite) => {
527 gpu.paged_write_in_place(base, &operands(1), out)?
528 }
529 Primitive::Fast(crate::fast::imp::FastOp::Softmax) => gpu.softmax_in_place(base, out)?,
530 Primitive::Fast(op) => gpu.norm_rope_in_place(base, op, &operands(1), out)?,
531 Primitive::SliceUpdate { .. } => {
532 let slices = primitive.dim_slices(out.shape.len())?;
533 gpu.slice_update_in_place(base, out, operands(1)[0], &slices)?
534 }
535 _ => {
536 let metas: Vec<&crate::array::Output> = inputs.iter().map(Array::out).collect();
537 let datas: Vec<Option<&Data>> = rest.iter().map(Option::as_ref).collect();
538 gpu.elementwise_in_place(primitive, &metas, &datas, base, out)?
539 }
540 };
541 Ok((vec![data], true))
542}
543
544fn input_data<'a>(
547 input: &'a Array,
548 gpu: &mut GpuEval,
549 pending: &mut bool,
550 foreign: &mut Vec<&'a Array>,
551) -> Result<Data> {
552 if let Some(d) = gpu.lookup(&input.node, input.output) {
553 *pending = true;
554 Ok(d)
555 } else if let Some(result) = input.out().slot.get() {
556 result.clone()
557 } else if let Some(d) = input.out().inflight.get() {
558 *pending = true;
561 foreign.push(input);
562 gpu.read_in_flight(input);
563 Ok(d)
564 } else {
565 gpu.commit();
569 wait_running(&input.node, gpu)?;
570 if let Some(result) = input.out().slot.get() {
571 return result.clone();
572 }
573 if let Some(d) = input.out().inflight.get() {
574 *pending = true;
575 foreign.push(input);
576 gpu.read_in_flight(input);
577 return Ok(d);
578 }
579 input.out().slot.wait().clone()
582 }
583}
584
585fn compute(
588 node: &Node,
589 primitive: &Primitive,
590 inputs: &[Array],
591 gpu: &mut GpuEval,
592) -> Result<(Vec<Data>, bool)> {
593 let mut data = Vec::with_capacity(inputs.len());
594 let mut pending = false;
595 let mut foreign = Vec::new();
597 for input in inputs {
598 data.push(input_data(input, gpu, &mut pending, &mut foreign)?);
599 }
600 let operands: Vec<Operand<'_>> = inputs
601 .iter()
602 .zip(&data)
603 .map(|(a, d)| (a.out(), d))
604 .collect();
605 let await_foreign = || -> Result<()> {
608 for input in &foreign {
609 input.out().slot.wait().clone()?;
610 }
611 Ok(())
612 };
613 if matches!(primitive, Primitive::Depends { .. }) {
614 gpu.release_unreferenced();
618 }
619 if let Some(views) = view_outputs(primitive, &operands)? {
620 gpu.depend_on(&foreign);
623 return Ok((views, pending));
624 }
625 let device = node.stream.device;
626 if device.kind != DeviceKind::Cpu {
627 if !gpu.available(device) {
628 return Err(Error::UnsupportedDevice(device));
629 }
630 if gpu.supports(primitive, &operands, &node.outputs) {
631 return Ok((gpu.run(primitive, &operands, &node.outputs)?, true));
632 }
633 }
634 await_foreign()?;
636 if pending {
637 gpu.flush()?;
638 }
639 Ok((cpu(primitive, &operands)?, false))
640}
641
642fn view_outputs(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Option<Vec<Data>>> {
645 let share = |offset| {
646 Some(vec![Data {
647 buffer: operands[0].1.buffer.clone(),
648 offset,
649 }])
650 };
651 Ok(match primitive {
652 Primitive::Transpose(_)
653 | Primitive::Broadcast(_)
654 | Primitive::StopGradient
655 | Primitive::Depends { .. } => share(operands[0].1.offset),
656 Primitive::Slice { .. } => {
657 let (meta, data) = operands[0];
658 let (_, delta) =
659 view::slice_geometry(&meta.strides, &primitive.dim_slices(meta.shape.len())?);
660 share(data.offset + delta)
661 }
662 Primitive::Custom(call) => Some(
663 operands[call.primals..]
664 .iter()
665 .map(|(_, data)| (*data).clone())
666 .collect(),
667 ),
668 Primitive::Reshape(shape) => {
669 let (meta, data) = operands[0];
670 view::nocopy_reshape(&meta.shape, &meta.strides, shape).and_then(|_| share(data.offset))
671 }
672 _ => None,
673 })
674}
675
676fn cpu(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Vec<Data>> {
678 let new = |buffer| Ok(vec![Data::new(buffer)]);
679 match primitive {
680 Primitive::Unary(op) => new(kernels::unary(*op, operands[0])?),
681 Primitive::Binary(op) => new(kernels::binary(*op, operands[0], operands[1])?),
682 Primitive::Select => new(kernels::select(operands[0], operands[1], operands[2])?),
683 Primitive::AsType(dtype) => new(kernels::cast(operands[0], *dtype)?),
684 Primitive::Reduce { op, axes } => new(kernels::reduce(*op, operands[0], axes)?),
685 Primitive::ArgReduce { op, axis } => new(kernels::arg_reduce(*op, operands[0], *axis)?),
686 Primitive::GatherAxis { axis } => {
687 new(kernels::gather_axis(operands[0], operands[1], *axis)?)
688 }
689 Primitive::ScatterAxis { axis, reduce } => new(kernels::scatter_axis(
690 operands[0],
691 operands[1],
692 operands[2],
693 *axis,
694 *reduce,
695 )?),
696 Primitive::Concatenate { axis } => {
697 let mut shape = operands[0].0.shape.clone();
698 shape[*axis] = operands.iter().map(|(m, _)| m.shape[*axis]).sum();
699 new(kernels::concatenate(operands, &shape, *axis)?)
700 }
701 Primitive::Sort { axis } => new(kernels::sort(operands[0], *axis, false)?),
702 Primitive::ArgSort { axis } => new(kernels::sort(operands[0], *axis, true)?),
703 Primitive::Scan {
704 op,
705 axis,
706 reverse,
707 inclusive,
708 } => new(kernels::scan(
709 *op,
710 operands[0],
711 *axis,
712 *reverse,
713 *inclusive,
714 )?),
715 Primitive::Matmul => new(kernels::matmul(operands[0], operands[1])?),
716 Primitive::AddMM { .. } => {
717 let (alpha, beta) = primitive.addmm_factors();
718 new(kernels::addmm(
719 operands[0],
720 operands[1],
721 operands[2],
722 alpha,
723 beta,
724 )?)
725 }
726 Primitive::RandomBits { shape, .. } => {
727 let n: usize = shape.iter().product();
728 new(kernels::random_bits(
729 operands[0],
730 n * primitive_width(primitive),
731 )?)
732 }
733 Primitive::Fast(op) => op.eval_cpu(operands),
734 Primitive::MetalKernel(_) => Err(Error::InvalidArgument(
735 "metal_kernel: custom kernels run on the GPU only".into(),
736 )),
737 Primitive::Compiled(fused) => fused.eval_cpu(operands),
738 Primitive::Quantize { group_size, bits } => {
739 let (packed, scales, biases) =
740 kernels::quantize(operands[0], *group_size, usize::from(*bits))?;
741 Ok(vec![
742 Data::new(packed),
743 Data::new(scales),
744 Data::new(biases),
745 ])
746 }
747 Primitive::Dequantize { group_size, bits } => new(kernels::dequantize(
748 operands[0],
749 operands[1],
750 operands[2],
751 *group_size,
752 usize::from(*bits),
753 )?),
754 Primitive::QuantizedMatmul {
755 group_size,
756 bits,
757 transpose,
758 } => new(kernels::quantized_matmul(
759 operands,
760 *group_size,
761 usize::from(*bits),
762 *transpose,
763 )?),
764 Primitive::SliceUpdate { .. } => new(kernels::slice_update(
765 operands[0],
766 operands[1],
767 &primitive.dim_slices(operands[0].0.shape.len())?,
768 )?),
769 Primitive::Reshape(_) | Primitive::Copy => {
771 new(kernels::contiguous(operands[0].0, operands[0].1)?)
772 }
773 Primitive::Transpose(_)
774 | Primitive::Broadcast(_)
775 | Primitive::Slice { .. }
776 | Primitive::StopGradient
777 | Primitive::Depends { .. }
778 | Primitive::Custom(_) => {
779 unreachable!("views are handled by view_outputs")
780 }
781 #[cfg(test)]
782 Primitive::AddMultiply => {
783 use crate::primitive::BinaryOp;
784 Ok(vec![
785 Data::new(kernels::binary(BinaryOp::Add, operands[0], operands[1])?),
786 Data::new(kernels::binary(
787 BinaryOp::Multiply,
788 operands[0],
789 operands[1],
790 )?),
791 ])
792 }
793 }
794}
795
796fn primitive_width(primitive: &Primitive) -> usize {
797 match primitive {
798 Primitive::RandomBits { width, .. } => usize::from(*width),
799 _ => unreachable!(),
800 }
801}
802
803#[cfg(test)]
805pub(crate) mod counter {
806 use std::cell::Cell;
807
808 thread_local!(static EVALUATED: Cell<usize> = const { Cell::new(0) });
809
810 pub(crate) fn inc() {
811 EVALUATED.set(EVALUATED.get() + 1);
812 }
813 pub(crate) fn take() -> usize {
814 EVALUATED.replace(0)
815 }
816}
817
818#[cfg(test)]
819mod tests {
820 use super::*;
821 use crate::buffer::live;
822
823 #[test]
824 fn shared_node_runs_once() {
825 let x = Array::from_slice(&[1.0f32, 2.0], [2]);
826 let shared = &x + &x;
827 let left = &shared * &x;
828 let right = &shared + &x;
829 let top = &left + &right;
830 counter::take();
831 eval(&[top.clone(), left.clone()]).unwrap();
832 assert_eq!(counter::take(), 4, "shared, left, right, top: once each");
833 top.eval().unwrap();
834 assert_eq!(counter::take(), 0, "already evaluated");
835 assert_eq!(top.to_vec::<f32>().unwrap(), vec![5.0, 14.0]);
836 }
837
838 #[test]
839 fn running_releases_its_completion() {
840 let x = Array::from_slice(&[1.0f32], [1]);
843 for finish in [true, false] {
844 let done = Arc::new(Completion::default());
845 let running = Running {
846 node: &x.node,
847 done: done.clone(),
848 finished: false,
849 };
850 if finish {
851 running.finish(State::Done);
852 } else {
853 drop(running);
854 }
855 assert_eq!(Arc::strong_count(&done), 1, "finish = {finish}");
856 }
857 }
858
859 #[test]
860 fn intermediates_freed() {
861 crate::set_default_stream(crate::Stream::default_for(crate::Device::cpu()));
864 let before = live::count();
865 let mut x = Array::from(0i64);
866 let one = Array::from(1i64);
867 let steps = if cfg!(miri) { 100 } else { 10_000 };
868 for _ in 0..steps {
869 x = &x + &one;
870 }
871 x.eval().unwrap();
872 drop(one);
873 assert_eq!(
874 live::count() - before,
875 1,
876 "only the result's buffer is alive"
877 );
878 assert_eq!(x.item::<i64>().unwrap(), steps as i64);
879 }
880
881 #[test]
882 fn multi_output_once() {
883 let a = Array::from_slice(&[2i32, 3], [2]);
884 let b = Array::from_slice(&[5i32, 7], [2]);
885 let mut outs = Array::from_op(Primitive::AddMultiply, vec![a, b]).unwrap();
886 let product = outs.pop().unwrap();
887 let sum = outs.pop().unwrap();
888 counter::take();
889 eval(&[sum.clone(), product.clone()]).unwrap();
890 assert_eq!(counter::take(), 1);
891 drop(sum);
892 assert_eq!(product.to_vec::<i32>().unwrap(), vec![10, 21]);
893 }
894
895 #[test]
896 fn slice_update_writes_a_strided_region() {
897 let base = crate::ops::imp::arange(0.0, 12.0, 1.0, crate::DType::I32)
899 .unwrap()
900 .try_reshape(&[4, 3])
901 .unwrap()
902 .t(); let update = Array::from_slice(&[-1i32, -2, -3, -4, -5, -6, -7, -8], [2, 4]);
904 let primitive = Primitive::SliceUpdate {
905 start: vec![0, 3],
906 step: vec![2, -1],
907 len: vec![2, 4],
908 };
909 let out = Array::from_op(primitive.clone(), vec![base.clone(), update.clone()])
910 .unwrap()
911 .remove(0);
912 assert_eq!(
913 out.to_vec::<i32>().unwrap(),
914 [-4, -3, -2, -1, 1, 4, 7, 10, -8, -7, -6, -5]
915 );
916 let wrong = Array::from_slice(&[0i32; 3], [3]);
917 assert!(Array::from_op(primitive, vec![base, wrong]).is_err());
918 }
919
920 #[test]
921 fn tracer_nodes_keep_their_graph_only_while_tracing() {
922 let x = Array::from(2.0f32);
923 let kept = std::cell::Cell::new(false);
924 let g = crate::grad(|x: &Array| {
925 let y = x * x;
926 y.eval()?;
927 kept.set(matches!(&*y.node.lock(), State::Retained { .. }));
928 Ok(y)
929 });
930 assert_eq!(g(&x).unwrap().item::<f32>().unwrap(), 4.0);
931 assert!(kept.get(), "retained inside a transform");
932 let traced = crate::vjp_flat(|xs| Ok(vec![&xs[0] * &xs[0]]), &[x], &[Array::from(1.0f32)])
933 .unwrap()
934 .0
935 .remove(0);
936 traced.eval().unwrap();
937 assert!(
938 matches!(&*traced.node.lock(), State::Done),
939 "outside a transform"
940 );
941 }
942
943 #[test]
944 fn huge_buffers_are_never_donated() {
945 assert!(donatable_size(1));
947 assert!(donatable_size(u32::MAX as usize - 1));
948 assert!(!donatable_size(u32::MAX as usize));
949 assert!(!donatable_size(1 << 33));
950 assert!(!donatable_size(0));
951 let base =
954 crate::ops::imp::broadcast_to(&Array::from(0.0f32), &[1 << 16, 1 << 16]).unwrap();
955 let update = Array::from_slice(&[1.0f32], [1, 1]);
956 let primitive = Primitive::SliceUpdate {
957 start: vec![0, 0],
958 step: vec![1, 1],
959 len: vec![1, 1],
960 };
961 let out = Array::from_op(primitive.clone(), vec![base.clone(), update.clone()])
962 .unwrap()
963 .remove(0);
964 let mut inputs = vec![base, update];
965 let mut gpu = GpuEval::new();
966 assert!(donation(&out.node, &primitive, &mut inputs, &mut gpu).is_none());
967 }
968
969 #[test]
970 fn evaluation_detaches_inputs() {
971 let a = Array::from(1u8);
972 let c = &a + &a;
973 assert!(c.primitive_t().is_some());
974 c.eval().unwrap();
975 assert_eq!(c.primitive_t(), None);
976 assert_eq!(Arc::strong_count(&a.node), 1, "c no longer references a");
977 }
978
979 fn positions(arrays: &[Array]) -> NodeMap<*const Node, usize> {
981 let order = topological_order(arrays);
982 let at: NodeMap<*const Node, usize> = order
983 .iter()
984 .enumerate()
985 .map(|(i, n)| (Arc::as_ptr(n), i))
986 .collect();
987 assert_eq!(at.len(), order.len(), "each node once");
988 for (i, node) in order.iter().enumerate() {
989 if let State::Pending { inputs, .. } = &*node.lock() {
990 for input in inputs {
991 if let Some(&j) = at.get(&Arc::as_ptr(&input.node)) {
992 assert!(j < i, "inputs come first");
993 }
994 }
995 }
996 }
997 at
998 }
999
1000 #[test]
1001 fn independent_work_is_ordered_side_by_side() {
1002 let x = Array::from_slice(&[1.0f32, 2.0], [2]);
1003 let (a1, b1) = (&x + 1.0, &x + 3.0);
1004 let (a2, b2) = (&a1 * 2.0, &b1 * 4.0);
1005 let top = &a2 + &b2;
1006 let at = positions(std::slice::from_ref(&top));
1007 let p = |a: &Array| at[&Arc::as_ptr(&a.node)];
1008 assert!(
1009 p(&a1).max(p(&b1)) < p(&a2).min(p(&b2)),
1010 "both chains advance together"
1011 );
1012 assert_eq!(top.to_vec::<f32>().unwrap(), [20.0, 26.0]);
1013 }
1014
1015 #[test]
1016 fn wide_and_shared_graphs_are_ordered() {
1017 let x = Array::from_slice(&[1i32], [1]);
1018 let one = Array::from_slice(&[1i32], [1]);
1019 let shared = &x + &x;
1020 let mut joined = shared.clone();
1022 for i in 0..100 {
1023 let mut c = &shared + &Array::from_slice(&[i], [1]);
1024 for _ in 0..i % 7 {
1025 c = &c * &one;
1026 }
1027 joined = &joined + &c;
1028 }
1029 positions(&[joined.clone(), shared.clone(), joined.clone()]);
1030 counter::take();
1031 eval(&[joined.clone(), shared.clone()]).unwrap();
1032 let nodes = 1 + 100 * 2 + (0..100).map(|i| i % 7).sum::<usize>();
1033 assert_eq!(counter::take(), nodes, "every node once");
1034 assert_eq!(
1035 joined.item::<i32>().unwrap(),
1036 2 + 100 * 2 + (0..100).sum::<i32>()
1037 );
1038 }
1039
1040 #[test]
1041 fn scheduled_evaluation_skips_work_done_meanwhile() {
1042 let x = Array::from_slice(&[1.0f32, 2.0], [2]);
1043 let shared = &x * &x;
1044 let top = &shared + &x;
1045 let scheduled = schedule(std::slice::from_ref(&top));
1046 assert_eq!(scheduled.arrays().len(), 1);
1047 counter::take();
1048 shared.eval().unwrap();
1049 scheduled.eval().unwrap();
1050 assert_eq!(counter::take(), 2, "shared once, then top");
1051 assert_eq!(top.to_vec::<f32>().unwrap(), [2.0, 6.0]);
1052 }
1053}