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, &mut *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(
260 node: &Node,
261 primitive: &Primitive,
262 inputs: &mut [Array],
263 gpu: &mut GpuEval,
264) -> Option<(usize, Data)> {
265 let device = node.stream.device;
266 if node.tracer || device.kind == DeviceKind::Cpu || !gpu.available(device) {
267 return None;
268 }
269 let base = match primitive {
270 Primitive::SliceUpdate { .. }
271 | Primitive::Fast(crate::fast::FastOp::NormRope { cache: true, .. })
272 | Primitive::Fast(crate::fast::FastOp::PagedWrite) => Some(0),
273 Primitive::Fast(crate::fast::FastOp::GatedDeltaSlots) => Some(5),
274 _ => None,
275 };
276 if let Some(at) = base {
277 return owned(&mut inputs[at], gpu).map(|data| (at, data));
278 }
279 let [out] = node.outputs.as_slice() else {
280 return None;
281 };
282 for i in in_place_inputs(primitive, inputs, out) {
283 if let Some(data) = owned(&mut inputs[i], gpu) {
284 return Some((i, data));
285 }
286 }
287 None
288}
289
290fn in_place_inputs(
293 primitive: &Primitive,
294 inputs: &[Array],
295 out: &crate::array::Output,
296) -> Vec<usize> {
297 use crate::{DType, UnaryOp};
298 let candidates: &[usize] = match primitive {
299 Primitive::Unary(op) if *op != UnaryOp::Conjugate => &[0],
300 Primitive::Binary(_) => &[0, 1],
301 Primitive::Select => &[1, 2],
302 Primitive::Fast(crate::fast::FastOp::Softmax) => &[0],
303 _ => &[],
304 };
305 let float = matches!(out.dtype, DType::F16 | DType::BF16 | DType::F32);
306 if !float || out.size == 0 || out.size >= u32::MAX as usize {
307 return Vec::new();
308 }
309 candidates
310 .iter()
311 .copied()
312 .filter(|&i| inputs[i].dtype() == out.dtype && inputs[i].shape() == out.shape.as_slice())
313 .collect()
314}
315
316fn owned(input: &mut Array, gpu: &mut GpuEval) -> Option<Data> {
328 let at = input.output as usize;
331 let base = &mut input.node;
332 let usable = |data: &Data, out: &crate::array::Output, handles: usize| {
333 out.flags.row_contiguous
334 && crate::gpu::writable(data, out)
335 && Arc::strong_count(&data.buffer) == handles
336 };
337 if let Some(out) = base.outputs.get(at)
339 && let Some(data) = gpu.take_pending(base, at, |data, handles| usable(data, out, handles))
340 {
341 return Some(data);
342 }
343 if Arc::strong_count(base) != 1 {
344 return None;
345 }
346 if let Some(base) = Arc::get_mut(base) {
348 let out = base.outputs.get_mut(at)?;
349 if let Some(Ok(data)) = out.slot.get() {
350 let handles = 1 + usize::from(out.inflight.get().is_some());
352 if !usable(data, out, handles) {
353 return None;
354 }
355 out.inflight.clear();
356 return match out.slot.take() {
357 Some(Ok(data)) => Some(data),
358 _ => None,
359 };
360 }
361 }
362 let out = base.outputs.get(at)?;
364 if out.slot.get().is_some() {
365 return None;
366 }
367 let mut state = out.inflight.lock();
368 let crate::array::InFlightState::Set(data) = &*state else {
369 return None;
370 };
371 if !usable(data, out, 1) {
372 return None;
373 }
374 match std::mem::replace(&mut *state, crate::array::InFlightState::Donated) {
375 crate::array::InFlightState::Set(data) => Some(data),
376 _ => None,
377 }
378}
379
380fn in_place(
383 node: &Node,
384 primitive: &Primitive,
385 inputs: &[Array],
386 (at, base): (usize, Data),
387 gpu: &mut GpuEval,
388) -> Result<(Vec<Data>, bool)> {
389 let (mut pending, mut foreign) = (false, Vec::new());
390 let mut rest = Vec::with_capacity(inputs.len());
391 for (i, a) in inputs.iter().enumerate() {
392 rest.push(match i == at {
393 true => None,
394 false => Some(input_data(a, gpu, &mut pending, &mut foreign)?),
395 });
396 }
397 let out = &node.outputs[0];
398 let operands = |skip: usize| -> Vec<Operand<'_>> {
399 inputs
400 .iter()
401 .zip(&rest)
402 .skip(skip)
403 .filter_map(|(a, d)| d.as_ref().map(|d| (a.out(), d)))
404 .collect()
405 };
406 if let Primitive::Fast(crate::fast::FastOp::GatedDeltaSlots) = primitive {
407 let pool = inputs[at].out();
408 let all = operands(0);
409 let datas = gpu.gated_delta_slots_in_place(&all, pool, base, &node.outputs)?;
410 return Ok((datas, true));
411 }
412 let data = match primitive {
413 Primitive::Fast(crate::fast::FastOp::PagedWrite) => {
414 gpu.paged_write_in_place(base, &operands(1), out)?
415 }
416 Primitive::Fast(crate::fast::FastOp::Softmax) => gpu.softmax_in_place(base, out)?,
417 Primitive::Fast(op) => gpu.norm_rope_in_place(base, op, &operands(1), out)?,
418 Primitive::SliceUpdate { .. } => {
419 let slices = primitive.dim_slices(out.shape.len())?;
420 gpu.slice_update_in_place(base, out, operands(1)[0], &slices)?
421 }
422 _ => {
423 let metas: Vec<&crate::array::Output> = inputs.iter().map(Array::out).collect();
424 let datas: Vec<Option<&Data>> = rest.iter().map(Option::as_ref).collect();
425 gpu.elementwise_in_place(primitive, &metas, &datas, base, out)?
426 }
427 };
428 Ok((vec![data], true))
429}
430
431fn input_data<'a>(
434 input: &'a Array,
435 gpu: &mut GpuEval,
436 pending: &mut bool,
437 foreign: &mut Vec<&'a Array>,
438) -> Result<Data> {
439 if let Some(d) = gpu.lookup(&input.node, input.output) {
440 *pending = true;
441 Ok(d)
442 } else if let Some(result) = input.out().slot.get() {
443 result.clone()
444 } else if let Some(d) = input.out().inflight.get() {
445 *pending = true;
448 foreign.push(input);
449 Ok(d)
450 } else {
451 gpu.commit();
454 input.out().slot.wait().clone()
455 }
456}
457
458fn compute(
461 node: &Node,
462 primitive: &Primitive,
463 inputs: &[Array],
464 gpu: &mut GpuEval,
465) -> Result<(Vec<Data>, bool)> {
466 let mut data = Vec::with_capacity(inputs.len());
467 let mut pending = false;
468 let mut foreign = Vec::new();
470 for input in inputs {
471 data.push(input_data(input, gpu, &mut pending, &mut foreign)?);
472 }
473 let operands: Vec<Operand<'_>> = inputs
474 .iter()
475 .zip(&data)
476 .map(|(a, d)| (a.out(), d))
477 .collect();
478 let await_foreign = || -> Result<()> {
481 for input in &foreign {
482 input.out().slot.wait().clone()?;
483 }
484 Ok(())
485 };
486 if let Some(views) = view_outputs(primitive, &operands)? {
487 gpu.depend_on(&foreign);
490 return Ok((views, pending));
491 }
492 let device = node.stream.device;
493 if device.kind != DeviceKind::Cpu {
494 if !gpu.available(device) {
495 return Err(Error::UnsupportedDevice(device));
496 }
497 if gpu.supports(primitive, &operands, &node.outputs) {
498 return Ok((gpu.run(primitive, &operands, &node.outputs)?, true));
499 }
500 }
501 await_foreign()?;
503 if pending {
504 gpu.flush()?;
505 }
506 Ok((cpu(primitive, &operands)?, false))
507}
508
509fn view_outputs(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Option<Vec<Data>>> {
512 let share = |offset| {
513 Some(vec![Data {
514 buffer: operands[0].1.buffer.clone(),
515 offset,
516 }])
517 };
518 Ok(match primitive {
519 Primitive::Transpose(_) | Primitive::Broadcast(_) | Primitive::StopGradient => {
520 share(operands[0].1.offset)
521 }
522 Primitive::Slice { .. } => {
523 let (meta, data) = operands[0];
524 let (_, delta) =
525 view::slice_geometry(&meta.strides, &primitive.dim_slices(meta.shape.len())?);
526 share(data.offset + delta)
527 }
528 Primitive::Custom(call) => Some(
529 operands[call.primals..]
530 .iter()
531 .map(|(_, data)| (*data).clone())
532 .collect(),
533 ),
534 Primitive::Reshape(shape) => {
535 let (meta, data) = operands[0];
536 view::nocopy_reshape(&meta.shape, &meta.strides, shape).and_then(|_| share(data.offset))
537 }
538 _ => None,
539 })
540}
541
542fn cpu(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Vec<Data>> {
544 let new = |buffer| Ok(vec![Data::new(buffer)]);
545 match primitive {
546 Primitive::Unary(op) => new(kernels::unary(*op, operands[0])?),
547 Primitive::Binary(op) => new(kernels::binary(*op, operands[0], operands[1])?),
548 Primitive::Select => new(kernels::select(operands[0], operands[1], operands[2])?),
549 Primitive::AsType(dtype) => new(kernels::cast(operands[0], *dtype)?),
550 Primitive::Reduce { op, axes } => new(kernels::reduce(*op, operands[0], axes)?),
551 Primitive::ArgReduce { op, axis } => new(kernels::arg_reduce(*op, operands[0], *axis)?),
552 Primitive::GatherAxis { axis } => {
553 new(kernels::gather_axis(operands[0], operands[1], *axis)?)
554 }
555 Primitive::ScatterAxis { axis, reduce } => new(kernels::scatter_axis(
556 operands[0],
557 operands[1],
558 operands[2],
559 *axis,
560 *reduce,
561 )?),
562 Primitive::Concatenate { axis } => {
563 let mut shape = operands[0].0.shape.clone();
564 shape[*axis] = operands.iter().map(|(m, _)| m.shape[*axis]).sum();
565 new(kernels::concatenate(operands, &shape, *axis)?)
566 }
567 Primitive::Sort { axis } => new(kernels::sort(operands[0], *axis, false)?),
568 Primitive::ArgSort { axis } => new(kernels::sort(operands[0], *axis, true)?),
569 Primitive::Scan {
570 op,
571 axis,
572 reverse,
573 inclusive,
574 } => new(kernels::scan(
575 *op,
576 operands[0],
577 *axis,
578 *reverse,
579 *inclusive,
580 )?),
581 Primitive::Matmul => new(kernels::matmul(operands[0], operands[1])?),
582 Primitive::AddMM { .. } => {
583 let (alpha, beta) = primitive.addmm_factors();
584 new(kernels::addmm(
585 operands[0],
586 operands[1],
587 operands[2],
588 alpha,
589 beta,
590 )?)
591 }
592 Primitive::RandomBits { shape, .. } => {
593 let n: usize = shape.iter().product();
594 new(kernels::random_bits(
595 operands[0],
596 n * primitive_width(primitive),
597 )?)
598 }
599 Primitive::Fast(op) => op.eval_cpu(operands),
600 Primitive::MetalKernel(_) => Err(Error::InvalidArgument(
601 "metal_kernel: custom kernels run on the GPU only".into(),
602 )),
603 Primitive::Compiled(fused) => fused.eval_cpu(operands),
604 Primitive::Quantize { group_size, bits } => {
605 let (packed, scales, biases) =
606 kernels::quantize(operands[0], *group_size, usize::from(*bits))?;
607 Ok(vec![
608 Data::new(packed),
609 Data::new(scales),
610 Data::new(biases),
611 ])
612 }
613 Primitive::Dequantize { group_size, bits } => new(kernels::dequantize(
614 operands[0],
615 operands[1],
616 operands[2],
617 *group_size,
618 usize::from(*bits),
619 )?),
620 Primitive::QuantizedMatmul {
621 group_size,
622 bits,
623 transpose,
624 } => new(kernels::quantized_matmul(
625 operands,
626 *group_size,
627 usize::from(*bits),
628 *transpose,
629 )?),
630 Primitive::SliceUpdate { .. } => new(kernels::slice_update(
631 operands[0],
632 operands[1],
633 &primitive.dim_slices(operands[0].0.shape.len())?,
634 )?),
635 Primitive::Reshape(_) => new(kernels::contiguous(operands[0].0, operands[0].1)?),
637 Primitive::Transpose(_)
638 | Primitive::Broadcast(_)
639 | Primitive::Slice { .. }
640 | Primitive::StopGradient
641 | Primitive::Custom(_) => {
642 unreachable!("views are handled by view_outputs")
643 }
644 #[cfg(test)]
645 Primitive::AddMultiply => {
646 use crate::primitive::BinaryOp;
647 Ok(vec![
648 Data::new(kernels::binary(BinaryOp::Add, operands[0], operands[1])?),
649 Data::new(kernels::binary(
650 BinaryOp::Multiply,
651 operands[0],
652 operands[1],
653 )?),
654 ])
655 }
656 }
657}
658
659fn primitive_width(primitive: &Primitive) -> usize {
660 match primitive {
661 Primitive::RandomBits { width, .. } => usize::from(*width),
662 _ => unreachable!(),
663 }
664}
665
666#[cfg(test)]
668pub(crate) mod counter {
669 use std::cell::Cell;
670
671 thread_local!(static EVALUATED: Cell<usize> = const { Cell::new(0) });
672
673 pub(crate) fn inc() {
674 EVALUATED.set(EVALUATED.get() + 1);
675 }
676 pub(crate) fn take() -> usize {
677 EVALUATED.replace(0)
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use super::*;
684 use crate::buffer::live;
685
686 #[test]
687 fn shared_node_runs_once() {
688 let x = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
689 let shared = &x + &x;
690 let left = &shared * &x;
691 let right = &shared + &x;
692 let top = &left + &right;
693 counter::take();
694 eval(&[top.clone(), left.clone()]).unwrap();
695 assert_eq!(counter::take(), 4, "shared, left, right, top: once each");
696 top.eval().unwrap();
697 assert_eq!(counter::take(), 0, "already evaluated");
698 assert_eq!(top.to_vec::<f32>().unwrap(), vec![5.0, 14.0]);
699 }
700
701 #[test]
702 fn intermediates_freed() {
703 crate::set_default_stream(crate::Stream::default_for(crate::Device::cpu()));
706 let before = live::count();
707 let mut x = Array::scalar(0i64);
708 let one = Array::scalar(1i64);
709 let steps = if cfg!(miri) { 100 } else { 10_000 };
710 for _ in 0..steps {
711 x = &x + &one;
712 }
713 x.eval().unwrap();
714 drop(one);
715 assert_eq!(
716 live::count() - before,
717 1,
718 "only the result's buffer is alive"
719 );
720 assert_eq!(x.item::<i64>().unwrap(), steps as i64);
721 }
722
723 #[test]
724 fn multi_output_once() {
725 let a = Array::from_slice(&[2i32, 3], &[2]).unwrap();
726 let b = Array::from_slice(&[5i32, 7], &[2]).unwrap();
727 let mut outs = Array::from_op(Primitive::AddMultiply, vec![a, b]).unwrap();
728 let product = outs.pop().unwrap();
729 let sum = outs.pop().unwrap();
730 counter::take();
731 eval(&[sum.clone(), product.clone()]).unwrap();
732 assert_eq!(counter::take(), 1);
733 drop(sum);
734 assert_eq!(product.to_vec::<i32>().unwrap(), vec![10, 21]);
735 }
736
737 #[test]
738 fn slice_update_writes_a_strided_region() {
739 let base = crate::arange(0.0, 12.0, 1.0, crate::DType::I32)
741 .unwrap()
742 .reshape(&[4, 3])
743 .unwrap()
744 .t()
745 .unwrap(); let update = Array::from_slice(&[-1i32, -2, -3, -4, -5, -6, -7, -8], &[2, 4]).unwrap();
747 let primitive = Primitive::SliceUpdate {
748 start: vec![0, 3],
749 step: vec![2, -1],
750 len: vec![2, 4],
751 };
752 let out = Array::from_op(primitive.clone(), vec![base.clone(), update.clone()])
753 .unwrap()
754 .remove(0);
755 assert_eq!(
756 out.to_vec::<i32>().unwrap(),
757 [-4, -3, -2, -1, 1, 4, 7, 10, -8, -7, -6, -5]
758 );
759 let wrong = Array::from_slice(&[0i32; 3], &[3]).unwrap();
760 assert!(Array::from_op(primitive, vec![base, wrong]).is_err());
761 }
762
763 #[test]
764 fn tracer_nodes_keep_their_graph_only_while_tracing() {
765 let x = Array::scalar(2.0f32);
766 let kept = std::cell::Cell::new(false);
767 let g = crate::grad(|x: &Array| {
768 let y = x * x;
769 y.eval()?;
770 kept.set(matches!(&*y.node.lock(), State::Retained { .. }));
771 Ok(y)
772 });
773 assert_eq!(g(&x).unwrap().item::<f32>().unwrap(), 4.0);
774 assert!(kept.get(), "retained inside a transform");
775 let traced = crate::vjp(
776 |xs| Ok(vec![&xs[0] * &xs[0]]),
777 &[x],
778 &[Array::scalar(1.0f32)],
779 )
780 .unwrap()
781 .0
782 .remove(0);
783 traced.eval().unwrap();
784 assert!(
785 matches!(&*traced.node.lock(), State::Done),
786 "outside a transform"
787 );
788 }
789
790 #[test]
791 fn evaluation_detaches_inputs() {
792 let a = Array::scalar(1u8);
793 let c = &a + &a;
794 assert!(c.primitive().is_some());
795 c.eval().unwrap();
796 assert_eq!(c.primitive(), None);
797 assert_eq!(Arc::strong_count(&a.node), 1, "c no longer references a");
798 }
799
800 fn positions(arrays: &[Array]) -> NodeMap<*const Node, usize> {
802 let order = topological_order(arrays);
803 let at: NodeMap<*const Node, usize> = order
804 .iter()
805 .enumerate()
806 .map(|(i, n)| (Arc::as_ptr(n), i))
807 .collect();
808 assert_eq!(at.len(), order.len(), "each node once");
809 for (i, node) in order.iter().enumerate() {
810 if let State::Pending { inputs, .. } = &*node.lock() {
811 for input in inputs {
812 if let Some(&j) = at.get(&Arc::as_ptr(&input.node)) {
813 assert!(j < i, "inputs come first");
814 }
815 }
816 }
817 }
818 at
819 }
820
821 #[test]
822 fn independent_work_is_ordered_side_by_side() {
823 let x = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
824 let (a1, b1) = (&x + 1.0, &x + 3.0);
825 let (a2, b2) = (&a1 * 2.0, &b1 * 4.0);
826 let top = &a2 + &b2;
827 let at = positions(std::slice::from_ref(&top));
828 let p = |a: &Array| at[&Arc::as_ptr(&a.node)];
829 assert!(
830 p(&a1).max(p(&b1)) < p(&a2).min(p(&b2)),
831 "both chains advance together"
832 );
833 assert_eq!(top.to_vec::<f32>().unwrap(), [20.0, 26.0]);
834 }
835
836 #[test]
837 fn wide_and_shared_graphs_are_ordered() {
838 let x = Array::from_slice(&[1i32], &[1]).unwrap();
839 let one = Array::from_slice(&[1i32], &[1]).unwrap();
840 let shared = &x + &x;
841 let mut joined = shared.clone();
843 for i in 0..100 {
844 let mut c = &shared + &Array::from_slice(&[i], &[1]).unwrap();
845 for _ in 0..i % 7 {
846 c = &c * &one;
847 }
848 joined = &joined + &c;
849 }
850 positions(&[joined.clone(), shared.clone(), joined.clone()]);
851 counter::take();
852 eval(&[joined.clone(), shared.clone()]).unwrap();
853 let nodes = 1 + 100 * 2 + (0..100).map(|i| i % 7).sum::<usize>();
854 assert_eq!(counter::take(), nodes, "every node once");
855 assert_eq!(
856 joined.item::<i32>().unwrap(),
857 2 + 100 * 2 + (0..100).sum::<i32>()
858 );
859 }
860
861 #[test]
862 fn scheduled_evaluation_skips_work_done_meanwhile() {
863 let x = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
864 let shared = &x * &x;
865 let top = &shared + &x;
866 let scheduled = schedule(std::slice::from_ref(&top));
867 assert_eq!(scheduled.arrays().len(), 1);
868 counter::take();
869 shared.eval().unwrap();
870 scheduled.eval().unwrap();
871 assert_eq!(counter::take(), 2, "shared once, then top");
872 assert_eq!(top.to_vec::<f32>().unwrap(), [2.0, 6.0]);
873 }
874}