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 Primitive::Fast(crate::fast::imp::FastOp::ConvSlots { .. }) => Some(2),
384 Primitive::Collective {
386 op:
387 crate::distributed::CollectiveOp::Reduce(_)
388 | crate::distributed::CollectiveOp::Send { .. },
389 ..
390 } => Some(0),
391 _ => None,
392 };
393 if let Some(at) = base {
394 if !donatable_size(inputs[at].size()) {
395 return None;
396 }
397 return owned(&mut inputs[at], gpu).map(|data| (at, data));
398 }
399 let [out] = node.outputs.as_slice() else {
400 return None;
401 };
402 for i in in_place_inputs(primitive, inputs, out) {
403 if let Some(data) = owned(&mut inputs[i], gpu) {
404 return Some((i, data));
405 }
406 }
407 None
408}
409
410fn donatable_size(size: usize) -> bool {
413 size > 0 && size < u32::MAX as usize
414}
415
416fn in_place_inputs(
419 primitive: &Primitive,
420 inputs: &[Array],
421 out: &crate::array::Output,
422) -> Vec<usize> {
423 use crate::{DType, UnaryOp};
424 let candidates: &[usize] = match primitive {
425 Primitive::Unary(op) if *op != UnaryOp::Conjugate => &[0],
426 Primitive::Binary(_) => &[0, 1],
427 Primitive::Select => &[1, 2],
428 Primitive::Fast(
429 crate::fast::imp::FastOp::Softmax | crate::fast::imp::FastOp::CausalSoftmax { .. },
430 ) => &[0],
431 _ => &[],
432 };
433 let float = matches!(out.dtype, DType::F16 | DType::BF16 | DType::F32);
434 if !float || out.size == 0 {
435 return Vec::new();
436 }
437 if out.size >= u32::MAX as usize {
439 let large = match primitive {
440 Primitive::Fast(_) => {
441 let axis = out.shape.last().copied().unwrap_or(1).max(1);
442 out.size / axis * 1024 < u32::MAX as usize
443 }
444 _ => {
445 let metas: Vec<&crate::array::Output> = inputs.iter().map(Array::out).collect();
446 crate::gpu::large_elementwise(&metas, &out.shape)
447 }
448 };
449 if !large {
450 return Vec::new();
451 }
452 }
453 candidates
454 .iter()
455 .copied()
456 .filter(|&i| inputs[i].dtype() == out.dtype && inputs[i].dims() == out.shape.as_slice())
457 .collect()
458}
459
460fn owned(input: &mut Array, gpu: &mut GpuEval) -> Option<Data> {
480 let at = input.output as usize;
483 let base = &mut input.node;
484 let usable = |data: &Data, out: &crate::array::Output, handles: usize| {
485 out.flags.row_contiguous
486 && crate::gpu::writable(data, out)
487 && Arc::strong_count(&data.buffer) == handles
488 };
489 if let Some(out) = base.outputs.get(at)
491 && let Some(data) = gpu.take_pending(base, at, |data, handles| usable(data, out, handles))
492 {
493 return Some(data);
494 }
495 let cell = base.outputs.get(at)?.inflight.clone();
499 let mut state = cell.lock();
500 if Arc::strong_count(base) != 1 {
501 return None;
502 }
503 if let Some(base) = Arc::get_mut(base) {
505 let out = base.outputs.get_mut(at)?;
506 if let Some(Ok(data)) = out.slot.get() {
507 let published = matches!(*state, crate::array::InFlightState::Set(_));
509 if !usable(data, out, 1 + usize::from(published)) {
510 return None;
511 }
512 if published {
513 *state = crate::array::InFlightState::Unset;
514 }
515 return match out.slot.take() {
516 Some(Ok(data)) => Some(data),
517 _ => None,
518 };
519 }
520 }
521 let out = base.outputs.get(at)?;
523 if out.slot.get().is_some() {
524 return None;
525 }
526 let crate::array::InFlightState::Set(data) = &*state else {
527 return None;
528 };
529 if !usable(data, out, 1) {
530 return None;
531 }
532 match std::mem::replace(&mut *state, crate::array::InFlightState::Donated) {
533 crate::array::InFlightState::Set(data) => Some(data),
534 _ => None,
535 }
536}
537
538fn owned_on_cpu(input: &mut Array) -> Option<Data> {
542 let at = input.output as usize;
543 let base = Arc::get_mut(&mut input.node)?;
544 let out = base.outputs.get_mut(at)?;
545 if out.inflight.get().is_some() {
546 return None;
547 }
548 let Some(Ok(data)) = out.slot.get() else {
549 return None;
550 };
551 let whole = data.offset == 0 && data.buffer.len() == out.size * out.dtype.size();
552 if !(out.flags.row_contiguous
553 && whole
554 && Arc::strong_count(&data.buffer) == 1
555 && data.buffer.cpu_writable())
556 {
557 return None;
558 }
559 match out.slot.take() {
560 Some(Ok(data)) => Some(data),
561 _ => None,
562 }
563}
564
565fn in_place(
568 node: &Node,
569 primitive: &Primitive,
570 inputs: &[Array],
571 (at, base): (usize, Data),
572 gpu: &mut GpuEval,
573) -> Result<(Vec<Data>, bool)> {
574 let (mut pending, mut foreign) = (false, Vec::new());
575 let mut rest = Vec::with_capacity(inputs.len());
576 for (i, a) in inputs.iter().enumerate() {
577 rest.push(match i == at {
578 true => None,
579 false => Some(input_data(a, gpu, &mut pending, &mut foreign)?),
580 });
581 }
582 let out = &node.outputs[0];
583 if let Primitive::Collective { op, group } = primitive {
584 let outputs = gpu.collective(op, *group, inputs, Vec::new(), out, Some(base))?;
585 return Ok((outputs, true));
586 }
587 let operands = |skip: usize| -> Vec<Operand<'_>> {
588 inputs
589 .iter()
590 .zip(&rest)
591 .skip(skip)
592 .filter_map(|(a, d)| d.as_ref().map(|d| (a.out(), d)))
593 .collect()
594 };
595 if let Primitive::Fast(crate::fast::imp::FastOp::GatedDeltaSlots) = primitive {
596 let pool = inputs[at].out();
597 let all = operands(0);
598 let datas = gpu.gated_delta_slots_in_place(&all, pool, base, &node.outputs)?;
599 return Ok((datas, true));
600 }
601 if let Primitive::Fast(crate::fast::imp::FastOp::ConvSlots { silu }) = primitive {
602 let pool = inputs[at].out();
603 let all = operands(0);
604 let datas = gpu.conv_slots_in_place(&all, pool, base, &node.outputs, *silu)?;
605 return Ok((datas, true));
606 }
607 let data = match primitive {
608 Primitive::Fast(crate::fast::imp::FastOp::PagedWrite) => {
609 gpu.paged_write_in_place(base, &operands(1), out)?
610 }
611 Primitive::Fast(crate::fast::imp::FastOp::Softmax) => {
612 gpu.softmax_in_place(base, out, None)?
613 }
614 Primitive::Fast(crate::fast::imp::FastOp::CausalSoftmax { rows }) => {
615 gpu.softmax_in_place(base, out, Some(*rows as usize))?
616 }
617 Primitive::Fast(op) => gpu.norm_rope_in_place(base, op, &operands(1), out)?,
618 Primitive::SliceUpdate { .. } if node.stream.device.kind == DeviceKind::Cpu => {
619 for input in &foreign {
621 input.out().slot.wait().clone()?;
622 }
623 if pending {
624 gpu.flush()?;
625 }
626 let slices = primitive.dim_slices(out.shape.len())?;
627 let mut base = base;
628 let buffer = Arc::get_mut(&mut base.buffer).expect("donated: the only handle");
629 crate::kernels::slice_update_into(buffer, &out.shape, operands(1)[0], &slices);
630 return Ok((vec![base], false));
631 }
632 Primitive::SliceUpdate { .. } => {
633 let slices = primitive.dim_slices(out.shape.len())?;
634 gpu.slice_update_in_place(base, out, operands(1)[0], &slices)?
635 }
636 _ => {
637 let metas: Vec<&crate::array::Output> = inputs.iter().map(Array::out).collect();
638 let datas: Vec<Option<&Data>> = rest.iter().map(Option::as_ref).collect();
639 gpu.elementwise_in_place(primitive, &metas, &datas, base, out)?
640 }
641 };
642 Ok((vec![data], true))
643}
644
645fn input_data<'a>(
648 input: &'a Array,
649 gpu: &mut GpuEval,
650 pending: &mut bool,
651 foreign: &mut Vec<&'a Array>,
652) -> Result<Data> {
653 if let Some(d) = gpu.lookup(&input.node, input.output) {
654 *pending = true;
655 Ok(d)
656 } else if let Some(result) = input.out().slot.get() {
657 result.clone()
658 } else if let Some(d) = input.out().inflight.get() {
659 *pending = true;
662 foreign.push(input);
663 gpu.read_in_flight(input);
664 Ok(d)
665 } else {
666 gpu.commit();
670 wait_running(&input.node, gpu)?;
671 if let Some(result) = input.out().slot.get() {
672 return result.clone();
673 }
674 if let Some(d) = input.out().inflight.get() {
675 *pending = true;
676 foreign.push(input);
677 gpu.read_in_flight(input);
678 return Ok(d);
679 }
680 input.out().slot.wait().clone()
683 }
684}
685
686fn compute(
689 node: &Node,
690 primitive: &Primitive,
691 inputs: &[Array],
692 gpu: &mut GpuEval,
693) -> Result<(Vec<Data>, bool)> {
694 let mut data = Vec::with_capacity(inputs.len());
695 let mut pending = false;
696 let mut foreign = Vec::new();
698 for input in inputs {
699 data.push(input_data(input, gpu, &mut pending, &mut foreign)?);
700 }
701 let operands: Vec<Operand<'_>> = inputs
702 .iter()
703 .zip(&data)
704 .map(|(a, d)| (a.out(), d))
705 .collect();
706 let await_foreign = || -> Result<()> {
709 for input in &foreign {
710 input.out().slot.wait().clone()?;
711 }
712 Ok(())
713 };
714 if matches!(primitive, Primitive::Depends { .. }) {
715 gpu.release_unreferenced();
719 }
720 if let Some(views) = view_outputs(primitive, &operands)? {
721 gpu.depend_on(&foreign);
724 return Ok((views, pending));
725 }
726 let device = node.stream.device;
727 if device.kind != DeviceKind::Cpu {
728 if !gpu.available(device) {
729 return Err(Error::UnsupportedDevice(device));
730 }
731 if gpu.supports(primitive, &operands, &node.outputs) {
732 return Ok((gpu.run(primitive, &operands, &node.outputs)?, true));
733 }
734 if let Primitive::Collective { op, group } = primitive {
735 gpu.depend_on(&foreign);
737 let outputs = gpu.collective(op, *group, inputs, data, &node.outputs[0], None)?;
738 return Ok((outputs, true));
739 }
740 }
741 if gpu.collectives_pending() {
744 gpu.commit();
745 }
746 await_foreign()?;
748 if pending {
749 gpu.flush()?;
750 }
751 Ok((cpu(primitive, &operands)?, false))
752}
753
754fn view_outputs(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Option<Vec<Data>>> {
757 let share = |offset| {
758 Some(vec![Data {
759 buffer: operands[0].1.buffer.clone(),
760 offset,
761 }])
762 };
763 Ok(match primitive {
764 Primitive::Transpose(_)
765 | Primitive::Broadcast(_)
766 | Primitive::StopGradient
767 | Primitive::Depends { .. } => share(operands[0].1.offset),
768 Primitive::Slice { .. } => {
769 let (meta, data) = operands[0];
770 let (_, delta) =
771 view::slice_geometry(&meta.strides, &primitive.dim_slices(meta.shape.len())?);
772 share(data.offset + delta)
773 }
774 Primitive::Custom(call) => Some(
775 operands[call.primals..]
776 .iter()
777 .map(|(_, data)| (*data).clone())
778 .collect(),
779 ),
780 Primitive::Reshape(shape) => {
781 let (meta, data) = operands[0];
782 view::nocopy_reshape(&meta.shape, &meta.strides, shape).and_then(|_| share(data.offset))
783 }
784 Primitive::Squeeze(_) | Primitive::ExpandDims(_) => share(operands[0].1.offset),
786 Primitive::View(dtype) => {
788 let (meta, data) = operands[0];
789 let (from, to) = (meta.dtype.size() as isize, dtype.size() as isize);
790 let bytes = data.offset * from;
791 let shared = from == to || (meta.flags.row_contiguous && bytes % to == 0);
792 shared.then(|| share(bytes / to)).flatten()
793 }
794 _ => None,
795 })
796}
797
798fn cpu(primitive: &Primitive, operands: &[Operand<'_>]) -> Result<Vec<Data>> {
800 let new = |buffer| Ok(vec![Data::new(buffer)]);
801 match primitive {
802 Primitive::Unary(op) => new(kernels::unary(*op, operands[0])?),
803 Primitive::Binary(op) => new(kernels::binary(*op, operands[0], operands[1])?),
804 Primitive::Select => new(kernels::select(operands[0], operands[1], operands[2])?),
805 Primitive::AsType(dtype) => new(kernels::cast(operands[0], *dtype)?),
806 Primitive::ToFp8 => new(kernels::to_fp8(operands[0])?),
807 Primitive::SortedOffsets { groups } => new(kernels::sorted_offsets(operands[0], *groups)?),
808 Primitive::FromFp8(dtype) => new(kernels::from_fp8(operands[0], *dtype)?),
809 Primitive::Reduce { op, axes } => new(kernels::reduce(*op, operands[0], axes)?),
810 Primitive::ArgReduce { op, axis } => new(kernels::arg_reduce(*op, operands[0], *axis)?),
811 Primitive::GatherAxis { axis } => {
812 new(kernels::gather_axis(operands[0], operands[1], *axis)?)
813 }
814 Primitive::ScatterAxis { axis, reduce } => new(kernels::scatter_axis(
815 operands[0],
816 operands[1],
817 operands[2],
818 *axis,
819 *reduce,
820 )?),
821 Primitive::Concatenate { axis } => {
822 let mut shape = operands[0].0.shape.clone();
823 shape[*axis] = operands.iter().map(|(m, _)| m.shape[*axis]).sum();
824 new(kernels::concatenate(operands, &shape, *axis)?)
825 }
826 Primitive::Sort { axis } => new(kernels::sort(operands[0], *axis, false)?),
827 Primitive::ArgSort { axis } => new(kernels::sort(operands[0], *axis, true)?),
828 Primitive::Scan {
829 op,
830 axis,
831 reverse,
832 inclusive,
833 } => new(kernels::scan(
834 *op,
835 operands[0],
836 *axis,
837 *reverse,
838 *inclusive,
839 )?),
840 Primitive::Matmul => new(kernels::matmul(operands[0], operands[1])?),
841 Primitive::AddMM { .. } => {
842 let (alpha, beta) = primitive.addmm_factors();
843 new(kernels::addmm(
844 operands[0],
845 operands[1],
846 operands[2],
847 alpha,
848 beta,
849 )?)
850 }
851 Primitive::RandomBits { shape, .. } => {
852 let n: usize = shape.iter().product();
853 new(kernels::random_bits(
854 operands[0],
855 n * primitive_width(primitive),
856 )?)
857 }
858 Primitive::Fast(op) => op.eval_cpu(operands),
859 Primitive::MetalKernel(_) => Err(Error::InvalidArgument(
860 "metal_kernel: custom kernels run on the GPU only".into(),
861 )),
862 Primitive::Compiled(fused) => fused.eval_cpu(operands),
863 Primitive::Collective { op, group } => crate::distributed::eval_cpu(op, *group, operands),
864 Primitive::Quantize { quant } => {
865 let (group_size, bits) = quant.checked("quantize")?;
866 if quant.mode != crate::QuantMode::Affine {
867 let (packed, scales) = kernels::fp_quantize(operands[0], group_size, bits)?;
868 return Ok(vec![Data::new(packed), Data::new(scales)]);
869 }
870 let (packed, scales, biases) = kernels::quantize(operands[0], group_size, bits)?;
871 Ok(vec![
872 Data::new(packed),
873 Data::new(scales),
874 Data::new(biases),
875 ])
876 }
877 Primitive::Dequantize { quant, dtype } => {
878 let (group_size, bits) = quant.checked("dequantize")?;
879 new(match quant.mode {
880 crate::QuantMode::Affine => {
881 kernels::dequantize(operands[0], operands[1], operands[2], group_size, bits)?
882 }
883 _ => kernels::fp_dequantize(operands[0], operands[1], group_size, bits, *dtype)?,
884 })
885 }
886 Primitive::QuantizedMatmul { quant, transpose } => {
887 let (group_size, bits) = quant.checked("quantized_matmul")?;
888 new(kernels::quantized_matmul(
889 operands, *quant, group_size, bits, *transpose,
890 )?)
891 }
892 Primitive::SliceUpdate { .. } => new(kernels::slice_update(
893 operands[0],
894 operands[1],
895 &primitive.dim_slices(operands[0].0.shape.len())?,
896 )?),
897 Primitive::Reshape(_)
900 | Primitive::Squeeze(_)
901 | Primitive::ExpandDims(_)
902 | Primitive::Copy
903 | Primitive::View(_) => new(kernels::contiguous(operands[0].0, operands[0].1)?),
904 Primitive::Transpose(_)
905 | Primitive::Broadcast(_)
906 | Primitive::Slice { .. }
907 | Primitive::StopGradient
908 | Primitive::Depends { .. }
909 | Primitive::Custom(_) => {
910 unreachable!("views are handled by view_outputs")
911 }
912 #[cfg(test)]
913 Primitive::AddMultiply => {
914 use crate::primitive::BinaryOp;
915 Ok(vec![
916 Data::new(kernels::binary(BinaryOp::Add, operands[0], operands[1])?),
917 Data::new(kernels::binary(
918 BinaryOp::Multiply,
919 operands[0],
920 operands[1],
921 )?),
922 ])
923 }
924 }
925}
926
927fn primitive_width(primitive: &Primitive) -> usize {
928 match primitive {
929 Primitive::RandomBits { width, .. } => usize::from(*width),
930 _ => unreachable!(),
931 }
932}
933
934#[cfg(test)]
936pub(crate) mod counter {
937 use std::cell::Cell;
938
939 thread_local!(static EVALUATED: Cell<usize> = const { Cell::new(0) });
940
941 pub(crate) fn inc() {
942 EVALUATED.set(EVALUATED.get() + 1);
943 }
944 pub(crate) fn take() -> usize {
945 EVALUATED.replace(0)
946 }
947}
948
949#[cfg(test)]
950mod tests {
951 use super::*;
952 use crate::buffer::live;
953
954 #[test]
955 fn shared_node_runs_once() {
956 let x = Array::from_slice(&[1.0f32, 2.0], [2]);
957 let shared = &x + &x;
958 let left = &shared * &x;
959 let right = &shared + &x;
960 let top = &left + &right;
961 counter::take();
962 eval(&[top.clone(), left.clone()]).unwrap();
963 assert_eq!(counter::take(), 4, "shared, left, right, top: once each");
964 top.eval().unwrap();
965 assert_eq!(counter::take(), 0, "already evaluated");
966 assert_eq!(top.to_vec::<f32>().unwrap(), vec![5.0, 14.0]);
967 }
968
969 #[test]
970 fn running_releases_its_completion() {
971 let x = Array::from_slice(&[1.0f32], [1]);
974 for finish in [true, false] {
975 let done = Arc::new(Completion::default());
976 let running = Running {
977 node: &x.node,
978 done: done.clone(),
979 finished: false,
980 };
981 if finish {
982 running.finish(State::Done);
983 } else {
984 drop(running);
985 }
986 assert_eq!(Arc::strong_count(&done), 1, "finish = {finish}");
987 }
988 }
989
990 #[test]
991 fn intermediates_freed() {
992 crate::set_default_stream(crate::Stream::default_for(crate::Device::cpu()));
995 let before = live::count();
996 let mut x = Array::from(0i64);
997 let one = Array::from(1i64);
998 let steps = if cfg!(miri) { 100 } else { 10_000 };
999 for _ in 0..steps {
1000 x = &x + &one;
1001 }
1002 x.eval().unwrap();
1003 drop(one);
1004 assert_eq!(
1005 live::count() - before,
1006 1,
1007 "only the result's buffer is alive"
1008 );
1009 assert_eq!(x.item::<i64>().unwrap(), steps as i64);
1010 }
1011
1012 #[test]
1013 fn multi_output_once() {
1014 let a = Array::from_slice(&[2i32, 3], [2]);
1015 let b = Array::from_slice(&[5i32, 7], [2]);
1016 let mut outs = Array::from_op(Primitive::AddMultiply, vec![a, b]).unwrap();
1017 let product = outs.pop().unwrap();
1018 let sum = outs.pop().unwrap();
1019 counter::take();
1020 eval(&[sum.clone(), product.clone()]).unwrap();
1021 assert_eq!(counter::take(), 1);
1022 drop(sum);
1023 assert_eq!(product.to_vec::<i32>().unwrap(), vec![10, 21]);
1024 }
1025
1026 #[test]
1027 fn slice_update_writes_a_strided_region() {
1028 let base = crate::ops::imp::arange(0.0, 12.0, 1.0, crate::DType::I32)
1030 .unwrap()
1031 .try_reshape(&[4, 3])
1032 .unwrap()
1033 .t(); let update = Array::from_slice(&[-1i32, -2, -3, -4, -5, -6, -7, -8], [2, 4]);
1035 let primitive = Primitive::SliceUpdate {
1036 start: vec![0, 3],
1037 step: vec![2, -1],
1038 len: vec![2, 4],
1039 };
1040 let out = Array::from_op(primitive.clone(), vec![base.clone(), update.clone()])
1041 .unwrap()
1042 .remove(0);
1043 assert_eq!(
1044 out.to_vec::<i32>().unwrap(),
1045 [-4, -3, -2, -1, 1, 4, 7, 10, -8, -7, -6, -5]
1046 );
1047 let wrong = Array::from_slice(&[0i32; 3], [3]);
1048 assert!(Array::from_op(primitive, vec![base, wrong]).is_err());
1049 }
1050
1051 #[test]
1052 fn tracer_nodes_keep_their_graph_only_while_tracing() {
1053 let x = Array::from(2.0f32);
1054 let kept = std::cell::Cell::new(false);
1055 let g = crate::grad(|x: &Array| {
1056 let y = x * x;
1057 y.eval()?;
1058 kept.set(matches!(&*y.node.lock(), State::Retained { .. }));
1059 Ok(y)
1060 });
1061 assert_eq!(g(&x).unwrap().item::<f32>().unwrap(), 4.0);
1062 assert!(kept.get(), "retained inside a transform");
1063 let traced = crate::vjp_flat(|xs| Ok(vec![&xs[0] * &xs[0]]), &[x], &[Array::from(1.0f32)])
1064 .unwrap()
1065 .0
1066 .remove(0);
1067 traced.eval().unwrap();
1068 assert!(
1069 matches!(&*traced.node.lock(), State::Done),
1070 "outside a transform"
1071 );
1072 }
1073
1074 #[test]
1075 fn huge_buffers_are_never_donated() {
1076 assert!(donatable_size(1));
1078 assert!(donatable_size(u32::MAX as usize - 1));
1079 assert!(!donatable_size(u32::MAX as usize));
1080 assert!(!donatable_size(1 << 33));
1081 assert!(!donatable_size(0));
1082 let base =
1085 crate::ops::imp::broadcast_to(&Array::from(0.0f32), &[1 << 16, 1 << 16]).unwrap();
1086 let update = Array::from_slice(&[1.0f32], [1, 1]);
1087 let primitive = Primitive::SliceUpdate {
1088 start: vec![0, 0],
1089 step: vec![1, 1],
1090 len: vec![1, 1],
1091 };
1092 let out = Array::from_op(primitive.clone(), vec![base.clone(), update.clone()])
1093 .unwrap()
1094 .remove(0);
1095 let mut inputs = vec![base, update];
1096 let mut gpu = GpuEval::new();
1097 assert!(donation(&out.node, &primitive, &mut inputs, &mut gpu).is_none());
1098 }
1099
1100 #[test]
1101 fn evaluation_detaches_inputs() {
1102 let a = Array::from(1u8);
1103 let c = &a + &a;
1104 assert!(c.primitive_t().is_some());
1105 c.eval().unwrap();
1106 assert_eq!(c.primitive_t(), None);
1107 assert_eq!(Arc::strong_count(&a.node), 1, "c no longer references a");
1108 }
1109
1110 fn positions(arrays: &[Array]) -> NodeMap<*const Node, usize> {
1112 let order = topological_order(arrays);
1113 let at: NodeMap<*const Node, usize> = order
1114 .iter()
1115 .enumerate()
1116 .map(|(i, n)| (Arc::as_ptr(n), i))
1117 .collect();
1118 assert_eq!(at.len(), order.len(), "each node once");
1119 for (i, node) in order.iter().enumerate() {
1120 if let State::Pending { inputs, .. } = &*node.lock() {
1121 for input in inputs {
1122 if let Some(&j) = at.get(&Arc::as_ptr(&input.node)) {
1123 assert!(j < i, "inputs come first");
1124 }
1125 }
1126 }
1127 }
1128 at
1129 }
1130
1131 #[test]
1132 fn independent_work_is_ordered_side_by_side() {
1133 let x = Array::from_slice(&[1.0f32, 2.0], [2]);
1134 let (a1, b1) = (&x + 1.0, &x + 3.0);
1135 let (a2, b2) = (&a1 * 2.0, &b1 * 4.0);
1136 let top = &a2 + &b2;
1137 let at = positions(std::slice::from_ref(&top));
1138 let p = |a: &Array| at[&Arc::as_ptr(&a.node)];
1139 assert!(
1140 p(&a1).max(p(&b1)) < p(&a2).min(p(&b2)),
1141 "both chains advance together"
1142 );
1143 assert_eq!(top.to_vec::<f32>().unwrap(), [20.0, 26.0]);
1144 }
1145
1146 #[test]
1147 fn wide_and_shared_graphs_are_ordered() {
1148 let x = Array::from_slice(&[1i32], [1]);
1149 let one = Array::from_slice(&[1i32], [1]);
1150 let shared = &x + &x;
1151 let mut joined = shared.clone();
1153 for i in 0..100 {
1154 let mut c = &shared + &Array::from_slice(&[i], [1]);
1155 for _ in 0..i % 7 {
1156 c = &c * &one;
1157 }
1158 joined = &joined + &c;
1159 }
1160 positions(&[joined.clone(), shared.clone(), joined.clone()]);
1161 counter::take();
1162 eval(&[joined.clone(), shared.clone()]).unwrap();
1163 let nodes = 1 + 100 * 2 + (0..100).map(|i| i % 7).sum::<usize>();
1164 assert_eq!(counter::take(), nodes, "every node once");
1165 assert_eq!(
1166 joined.item::<i32>().unwrap(),
1167 2 + 100 * 2 + (0..100).sum::<i32>()
1168 );
1169 }
1170
1171 #[test]
1172 fn scheduled_evaluation_skips_work_done_meanwhile() {
1173 let x = Array::from_slice(&[1.0f32, 2.0], [2]);
1174 let shared = &x * &x;
1175 let top = &shared + &x;
1176 let scheduled = schedule(std::slice::from_ref(&top));
1177 assert_eq!(scheduled.arrays().len(), 1);
1178 counter::take();
1179 shared.eval().unwrap();
1180 scheduled.eval().unwrap();
1181 assert_eq!(counter::take(), 2, "shared once, then top");
1182 assert_eq!(top.to_vec::<f32>().unwrap(), [2.0, 6.0]);
1183 }
1184}