1#![forbid(unsafe_code)]
39
40use std::cell::Cell;
41use std::collections::{BinaryHeap, HashMap, HashSet};
42use std::sync::{Arc, Mutex, PoisonError};
43
44use nuvec_core::graph::{self, Fused, FusedRef, Primitive};
45use nuvec_core::tree::{Tree, TreeDef};
46use nuvec_core::{
47 Array, Complex64, DType, DeviceKind, Error, IntoResult, Result, Stream, bf16, default_stream,
48 f16, random,
49};
50
51fn dims(a: &Array) -> Vec<usize> {
53 a.shape().iter().map(|&d| d as usize).collect()
54}
55
56pub const VERSION: &str = env!("CARGO_PKG_VERSION");
58
59const MAX_FUSED_STEPS: usize = 32;
61const MAX_FUSED_ARRAYS: usize = 24;
64
65#[derive(Clone, Debug)]
67#[non_exhaustive]
68pub struct Options {
69 pub cache_capacity: usize,
72}
73
74impl Default for Options {
75 fn default() -> Self {
76 Self { cache_capacity: 64 }
77 }
78}
79
80impl Options {
81 pub fn cache_capacity(mut self, n: usize) -> Self {
83 self.cache_capacity = n;
84 self
85 }
86
87 pub fn compile<'f, I, O, F, R>(self, f: F) -> impl Fn(&I) -> Result<O> + 'f
89 where
90 I: Tree,
91 O: Tree,
92 F: Fn(&I) -> R + 'f,
93 R: IntoResult<Ok = O>,
94 {
95 let cache: Mutex<Cache<(Tape, O)>> = Mutex::new(Cache::new(self.cache_capacity));
96 move |inputs: &I| {
97 let leaves = inputs.leaves();
98 if leaves.iter().any(|a| graph::is_tracer(a) || a.is_failed()) {
99 return f(inputs).into_result();
102 }
103 let key = Key::new(vec![inputs.def()], &leaves);
104 let cached = lock(&cache).get(&key);
105 let entry = match cached {
106 Some(entry) => entry,
107 None => {
108 let mut out = None;
109 let mut body = |xs: &[Array]| -> Result<Vec<Array>> {
110 let o = f(&inputs.unflatten(xs.iter().cloned())?).into_result()?;
111 let leaves = o.leaves();
112 out = Some(o.map(|_, a| light(a)));
113 Ok(leaves)
114 };
115 let tape = Tape::trace(&mut body, &leaves, key.stream)?;
116 let out =
117 out.ok_or_else(|| Error::InvalidArgument("compile: no output".into()))?;
118 let entry = Arc::new((tape, out));
119 if !entry.0.captures_tracer() {
120 lock(&cache).insert(key, entry.clone());
121 }
122 entry
123 }
124 };
125 let (tape, template) = &*entry;
126 template.unflatten(tape.replay(&leaves)?)
127 }
128 }
129
130 pub fn compile_mut<'f, S, I, O, F, R>(self, f: F) -> impl Fn(&mut S, &I) -> Result<O> + 'f
132 where
133 S: Tree,
134 I: Tree,
135 O: Tree,
136 F: Fn(&mut S, &I) -> R + 'f,
137 R: IntoResult<Ok = O>,
138 {
139 let cache: Mutex<Cache<MutTape<S, O>>> = Mutex::new(Cache::new(self.cache_capacity));
140 let changed_last = Cell::new(false);
141 move |state: &mut S, inputs: &I| {
142 let state_leaves = state.leaves();
143 let input_leaves = inputs.leaves();
144 let direct = state_leaves
145 .iter()
146 .chain(&input_leaves)
147 .any(|a| graph::is_tracer(a) || a.is_failed());
148 if direct {
149 return f(state, inputs).into_result();
151 }
152 let key0 = random::sequence_key();
153 let pre_def = state.def();
154 let mut all = Vec::with_capacity(1 + state_leaves.len() + input_leaves.len());
155 all.push(key0.clone());
156 all.extend(state_leaves.iter().cloned());
157 all.extend(input_leaves.iter().cloned());
158 let key = Key::new(vec![pre_def.clone(), inputs.def()], &all);
159 let cached = lock(&cache).get(&key);
160 let entry = match cached {
161 Some(entry) => entry,
162 None => {
163 let entry = Arc::new(MutTape::trace(&f, state, inputs, &all, key.stream)?);
164 if !entry.tape.captures_tracer() {
165 lock(&cache).insert(key, entry.clone());
166 }
167 entry
168 }
169 };
170 let changed = entry.post_def != pre_def;
171 if changed && changed_last.get() {
172 return Err(Error::InvalidArgument(
173 "compile_mut: state structure keeps changing; call opt.init(&model) before \
174 the loop, or make changing host fields arrays (or `static_unchecked`)"
175 .into(),
176 ));
177 }
178 let mut outputs = entry.tape.replay(&all)?;
179 let next_key = outputs.pop().expect("the key is the last output");
180 let state_out = outputs.split_off(entry.outputs);
181 let out = entry.out.unflatten(outputs)?;
182 let next = entry.post.unflatten(state_out)?;
183 changed_last.set(changed);
184 *state = next;
185 random::set_sequence_key(next_key);
186 Ok(out)
187 }
188 }
189}
190
191pub fn compile<'f, I, O, F, R>(f: F) -> impl Fn(&I) -> Result<O> + 'f
214where
215 I: Tree,
216 O: Tree,
217 F: Fn(&I) -> R + 'f,
218 R: IntoResult<Ok = O>,
219{
220 Options::default().compile(f)
221}
222
223pub fn compile_mut<'f, S, I, O, F, R>(f: F) -> impl Fn(&mut S, &I) -> Result<O> + 'f
264where
265 S: Tree,
266 I: Tree,
267 O: Tree,
268 F: Fn(&mut S, &I) -> R + 'f,
269 R: IntoResult<Ok = O>,
270{
271 Options::default().compile_mut(f)
272}
273
274pub fn compile_flat<F>(f: F) -> impl Fn(&[Array]) -> Result<Vec<Array>>
288where
289 F: Fn(&[Array]) -> Result<Vec<Array>>,
290{
291 let cache: Mutex<Cache<Tape>> = Mutex::new(Cache::new(Options::default().cache_capacity));
292 move |inputs| {
293 if inputs.iter().any(|a| graph::is_tracer(a) || a.is_failed()) {
294 return f(inputs);
295 }
296 let key = Key::new(Vec::new(), inputs);
297 let cached = lock(&cache).get(&key);
298 let tape = match cached {
299 Some(tape) => tape,
300 None => {
301 let tape = Arc::new(Tape::trace(&mut |xs: &[Array]| f(xs), inputs, key.stream)?);
302 if !tape.captures_tracer() {
303 lock(&cache).insert(key, tape.clone());
304 }
305 tape
306 }
307 };
308 tape.replay(inputs)
309 }
310}
311
312fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
313 m.lock().unwrap_or_else(PoisonError::into_inner)
314}
315
316fn light(a: &Array) -> Array {
318 graph::placeholder(a.dtype(), a.shape()).unwrap_or_else(|_| a.clone())
319}
320
321struct MutTape<S, O> {
324 tape: Tape,
325 outputs: usize,
326 out: O,
327 post: S,
328 post_def: TreeDef,
329}
330
331impl<S: Tree, O: Tree> MutTape<S, O> {
332 fn trace<I: Tree, R: IntoResult<Ok = O>>(
335 f: &dyn Fn(&mut S, &I) -> R,
336 state: &S,
337 inputs: &I,
338 all: &[Array],
339 stream: Stream,
340 ) -> Result<Self> {
341 let ns = state.len();
342 let key0 = all[0].clone();
343 let mut traced: Option<(O, S)> = None;
344 let mut body = |xs: &[Array]| -> Result<Vec<Array>> {
345 random::set_sequence_key(xs[0].clone());
346 let mut s = state.unflatten(xs[1..1 + ns].iter().cloned())?;
347 let i = inputs.unflatten(xs[1 + ns..].iter().cloned())?;
348 let out = f(&mut s, &i).into_result();
349 let key = random::sequence_key();
350 random::set_sequence_key(key0.clone());
351 let out = out?;
352 let mut leaves = out.leaves();
353 leaves.extend(s.leaves());
354 leaves.push(key);
355 traced = Some((out, s));
356 Ok(leaves)
357 };
358 let result = Tape::trace(&mut body, all, stream);
359 random::set_sequence_key(key0.clone());
360 let tape = result?;
361 let (out, post) =
362 traced.ok_or_else(|| Error::InvalidArgument("compile_mut: no output".into()))?;
363 Ok(Self {
364 tape,
365 outputs: out.len(),
366 out: out.map(|_, a| light(a)),
367 post_def: post.def(),
368 post: post.map(|_, a| light(a)),
369 })
370 }
371}
372
373struct Cache<T> {
375 tapes: HashMap<Key, (Arc<T>, u64)>,
376 clock: u64,
378 capacity: usize,
379}
380
381impl<T> Cache<T> {
382 fn new(capacity: usize) -> Self {
383 Self {
384 tapes: HashMap::new(),
385 clock: 0,
386 capacity,
387 }
388 }
389
390 fn get(&mut self, key: &Key) -> Option<Arc<T>> {
391 self.clock += 1;
392 let (tape, used) = self.tapes.get_mut(key)?;
393 *used = self.clock;
394 Some(tape.clone())
395 }
396
397 fn insert(&mut self, key: Key, tape: Arc<T>) {
398 if self.capacity == 0 {
399 return;
400 }
401 self.clock += 1;
402 if self.tapes.len() >= self.capacity
403 && !self.tapes.contains_key(&key)
404 && let Some(oldest) = self
405 .tapes
406 .iter()
407 .min_by_key(|(_, (_, used))| *used)
408 .map(|(k, _)| k.clone())
409 {
410 self.tapes.remove(&oldest);
411 }
412 self.tapes.insert(key, (tape, self.clock));
413 }
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Hash)]
419struct Key {
420 stream: Stream,
421 defs: Vec<TreeDef>,
422 inputs: Vec<(DType, Vec<usize>)>,
423}
424
425impl Key {
426 fn new(defs: Vec<TreeDef>, inputs: &[Array]) -> Self {
427 Self {
428 stream: default_stream(),
429 defs,
430 inputs: inputs.iter().map(|a| (a.dtype(), dims(a))).collect(),
431 }
432 }
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
437enum Ref {
438 Input(usize),
440 Const(usize),
442 Step(usize, usize),
444}
445
446#[derive(Debug, Clone)]
447struct Step {
448 primitive: Primitive,
449 inputs: Vec<Ref>,
450 outputs: Vec<(DType, Vec<usize>)>,
452}
453
454#[derive(Debug)]
456struct Tape {
457 consts: Vec<Array>,
458 steps: Vec<Step>,
459 outputs: Vec<Ref>,
460}
461
462impl Tape {
463 fn trace(
466 f: &mut dyn FnMut(&[Array]) -> Result<Vec<Array>>,
467 inputs: &[Array],
468 stream: Stream,
469 ) -> Result<Self> {
470 let placeholders = inputs
471 .iter()
472 .map(|a| graph::placeholder(a.dtype(), a.shape()))
473 .collect::<Result<Vec<_>>>()?;
474 let outputs = f(&placeholders)?;
475 let mut tape = Builder::new(&placeholders).build(&outputs);
476 if stream.device.kind == DeviceKind::Gpu {
477 tape.fuse(inputs);
478 }
479 Ok(tape)
480 }
481
482 fn captures_tracer(&self) -> bool {
485 self.consts.iter().any(graph::is_tracer)
486 }
487
488 fn replay(&self, inputs: &[Array]) -> Result<Vec<Array>> {
489 let mut values: Vec<Vec<Array>> = Vec::with_capacity(self.steps.len());
490 let get = |r: &Ref, values: &[Vec<Array>]| match *r {
491 Ref::Input(i) => inputs[i].clone(),
492 Ref::Const(c) => self.consts[c].clone(),
493 Ref::Step(s, o) => values[s][o].clone(),
494 };
495 for step in &self.steps {
496 let args = step.inputs.iter().map(|r| get(r, &values)).collect();
497 values.push(graph::from_primitive(step.primitive.clone(), args)?);
498 }
499 Ok(self.outputs.iter().map(|r| get(r, &values)).collect())
500 }
501
502 fn dtype(&self, r: Ref, inputs: &[Array]) -> DType {
503 match r {
504 Ref::Input(i) => inputs[i].dtype(),
505 Ref::Const(c) => self.consts[c].dtype(),
506 Ref::Step(s, o) => self.steps[s].outputs[o].0,
507 }
508 }
509
510 fn fuse(&mut self, inputs: &[Array]) {
516 let n = self.steps.len();
517 let fusable: Vec<bool> = self
518 .steps
519 .iter()
520 .map(|s| {
521 let dtypes: Vec<DType> = s.inputs.iter().map(|&r| self.dtype(r, inputs)).collect();
522 s.outputs.len() == 1 && Fused::supports(&s.primitive, &dtypes)
523 })
524 .collect();
525 let mut consumers: Vec<Vec<usize>> = vec![Vec::new(); n];
526 for (k, s) in self.steps.iter().enumerate() {
527 for r in &s.inputs {
528 if let Ref::Step(j, _) = *r {
529 consumers[j].push(k);
530 }
531 }
532 }
533 let mut is_output = vec![false; n];
534 for r in &self.outputs {
535 if let Ref::Step(j, _) = *r {
536 is_output[j] = true;
537 }
538 }
539 let step_inputs = |k: usize| {
540 self.steps[k].inputs.iter().filter_map(|r| match *r {
541 Ref::Step(j, _) => Some(j),
542 _ => None,
543 })
544 };
545 let mut group: Vec<Option<usize>> = vec![None; n];
547 let mut groups: HashMap<usize, (Vec<usize>, Vec<Ref>)> = HashMap::new();
549 for root in (0..n).rev() {
550 if group[root].is_some() || !fusable[root] {
551 continue;
552 }
553 let mut members = HashSet::from([root]);
554 let mut external: Vec<Ref> = Vec::new();
555 let mut outputs = 1;
556 let add_inputs = |k: usize, external: &mut Vec<Ref>| {
557 for &r in &self.steps[k].inputs {
558 if !external.contains(&r) {
559 external.push(r);
560 }
561 }
562 };
563 add_inputs(root, &mut external);
564 let mut candidates: BinaryHeap<usize> = step_inputs(root).collect();
566 let mut seen = HashSet::new();
567 while let Some(j) = candidates.pop() {
568 if !seen.insert(j)
569 || group[j].is_some()
570 || !fusable[j]
571 || members.len() >= MAX_FUSED_STEPS
572 || !consumers[j].iter().all(|c| members.contains(c))
573 {
574 continue;
575 }
576 let new = self.steps[j]
577 .inputs
578 .iter()
579 .filter(|r| !external.contains(r))
580 .collect::<HashSet<_>>()
581 .len();
582 let arrays = external.len() - 1 + new + outputs + usize::from(is_output[j]);
583 if arrays > MAX_FUSED_ARRAYS {
584 continue;
585 }
586 members.insert(j);
587 outputs += usize::from(is_output[j]);
588 external.retain(|&r| r != Ref::Step(j, 0));
589 add_inputs(j, &mut external);
590 candidates.extend(step_inputs(j));
591 }
592 if members.len() < 2 || external.len() + outputs > MAX_FUSED_ARRAYS {
593 continue;
594 }
595 let mut members: Vec<usize> = members.into_iter().collect();
596 members.sort_unstable();
597 for &m in &members {
598 group[m] = Some(root);
599 }
600 groups.insert(root, (members, external));
601 }
602 if groups.is_empty() {
603 return;
604 }
605 let mut index: Vec<Option<(usize, usize)>> = vec![None; n];
608 let remap = |r: Ref, index: &[Option<(usize, usize)>]| match r {
609 Ref::Step(j, o) => {
610 let (k, offset) = index[j].expect("defined before use");
611 Ref::Step(k, offset + o)
612 }
613 r => r,
614 };
615 let mut steps = Vec::with_capacity(n);
616 for k in 0..n {
617 let step = match (group[k], groups.get(&k)) {
618 (Some(_), None) => continue, (Some(_), Some((members, external))) => {
620 let position: HashMap<usize, usize> =
621 members.iter().enumerate().map(|(p, &m)| (m, p)).collect();
622 let graph = members
623 .iter()
624 .map(|&m| {
625 let args = self.steps[m]
626 .inputs
627 .iter()
628 .map(|r| match *r {
629 Ref::Step(j, _) if position.contains_key(&j) => {
630 FusedRef::Step(position[&j])
631 }
632 r => FusedRef::Input(
633 external.iter().position(|&e| e == r).expect("external"),
634 ),
635 })
636 .collect();
637 (self.steps[m].primitive.clone(), args)
638 })
639 .collect();
640 let outs: Vec<usize> = members
642 .iter()
643 .copied()
644 .filter(|&m| m != k && is_output[m])
645 .chain([k])
646 .collect();
647 for (o, &m) in outs.iter().enumerate() {
648 index[m] = Some((steps.len(), o));
649 }
650 let dtypes: Vec<DType> =
651 external.iter().map(|&r| self.dtype(r, inputs)).collect();
652 let positions: Vec<usize> = outs.iter().map(|m| position[m]).collect();
653 let Ok(f) = Fused::new(&dtypes, graph, &positions) else {
654 unreachable!("every member is fusable")
655 };
656 steps.push(Step {
657 primitive: Primitive::Compiled(f),
658 inputs: external.iter().map(|&r| remap(r, &index)).collect(),
659 outputs: outs
660 .iter()
661 .map(|&m| self.steps[m].outputs[0].clone())
662 .collect(),
663 });
664 continue;
665 }
666 (None, _) => {
667 let s = &self.steps[k];
668 Step {
669 primitive: s.primitive.clone(),
670 inputs: s.inputs.iter().map(|&r| remap(r, &index)).collect(),
671 outputs: s.outputs.clone(),
672 }
673 }
674 };
675 index[k] = Some((steps.len(), 0));
676 steps.push(step);
677 }
678 self.outputs = self.outputs.iter().map(|&r| remap(r, &index)).collect();
679 self.steps = steps;
680 }
681}
682
683struct Builder {
685 refs: HashMap<(usize, usize), Ref>,
687 consts: Vec<Array>,
688 scalars: HashMap<(DType, Vec<usize>, u64), usize>,
690 steps: Vec<Step>,
691 seen: HashMap<(Primitive, Vec<Ref>), usize>,
693}
694
695impl Builder {
696 fn new(placeholders: &[Array]) -> Self {
697 let refs = placeholders
698 .iter()
699 .enumerate()
700 .map(|(i, p)| ((graph::id(p), graph::output_index(p)), Ref::Input(i)))
701 .collect();
702 Self {
703 refs,
704 consts: Vec::new(),
705 scalars: HashMap::new(),
706 steps: Vec::new(),
707 seen: HashMap::new(),
708 }
709 }
710
711 fn key(a: &Array) -> (usize, usize) {
712 (graph::id(a), graph::output_index(a))
713 }
714
715 fn build(mut self, outputs: &[Array]) -> Tape {
718 let mut stack: Vec<(Array, bool)> = outputs.iter().map(|a| (a.clone(), false)).collect();
719 while let Some((a, expanded)) = stack.pop() {
720 if self.refs.contains_key(&Self::key(&a)) {
721 continue;
722 }
723 let inputs = graph::inputs(&a);
724 if expanded || inputs.is_empty() {
725 self.resolve(&a, &inputs);
726 continue;
727 }
728 stack.push((a, true));
729 stack.extend(
730 inputs
731 .into_iter()
732 .filter(|i| !self.refs.contains_key(&Self::key(i)))
733 .map(|i| (i, false)),
734 );
735 }
736 let outputs = outputs.iter().map(|a| self.refs[&Self::key(a)]).collect();
737 Tape {
738 consts: self.consts,
739 steps: self.steps,
740 outputs,
741 }
742 }
743
744 fn resolve(&mut self, a: &Array, inputs: &[Array]) {
748 let siblings = graph::siblings(a);
749 let args: Vec<Ref> = inputs.iter().map(|i| self.refs[&Self::key(i)]).collect();
750 let primitive = graph::primitive(a);
751 let Some(primitive) =
752 primitive.filter(|_| !args.iter().all(|r| matches!(r, Ref::Const(_))))
753 else {
754 for s in &siblings {
755 let c = self.constant(s);
756 self.refs.insert(Self::key(s), Ref::Const(c));
757 }
758 return;
759 };
760 if siblings.len() == 1 && is_identity(&primitive, &inputs[0], a) {
761 self.refs.insert(Self::key(a), args[0]);
762 return;
763 }
764 let k = match self.seen.get(&(primitive.clone(), args.clone())) {
765 Some(&k) => k,
766 None => {
767 self.steps.push(Step {
768 primitive: primitive.clone(),
769 inputs: args.clone(),
770 outputs: siblings.iter().map(|s| (s.dtype(), dims(s))).collect(),
771 });
772 let k = self.steps.len() - 1;
773 self.seen.insert((primitive, args), k);
774 k
775 }
776 };
777 for (o, s) in siblings.iter().enumerate() {
778 self.refs.insert(Self::key(s), Ref::Step(k, o));
779 }
780 }
781
782 fn constant(&mut self, a: &Array) -> usize {
786 if graph::is_tracer(a) {
787 self.consts.push(a.clone());
788 return self.consts.len() - 1;
789 }
790 let value = scalar_bits(a).or_else(|| {
791 match (graph::primitive(a), graph::inputs(a).as_slice()) {
793 (Some(Primitive::Broadcast(_)), [x]) => scalar_bits(x),
794 _ => None,
795 }
796 });
797 let key = value.map(|v| (a.dtype(), dims(a), v));
798 if let Some(&c) = key.as_ref().and_then(|k| self.scalars.get(k)) {
799 return c;
800 }
801 self.consts.push(a.clone());
802 let c = self.consts.len() - 1;
803 if let Some(key) = key {
804 self.scalars.insert(key, c);
805 }
806 c
807 }
808}
809
810fn is_identity(primitive: &Primitive, input: &Array, output: &Array) -> bool {
812 let same = input.shape() == output.shape() && graph::strides(input) == graph::strides(output);
813 match primitive {
814 Primitive::Broadcast(_) | Primitive::Reshape(_) | Primitive::StopGradient => same,
815 Primitive::Transpose(axes) => axes.iter().enumerate().all(|(i, &a)| i == a),
816 Primitive::AsType(d) => *d == input.dtype(),
817 _ => false,
818 }
819}
820
821fn scalar_bits(a: &Array) -> Option<u64> {
823 if a.size() != 1 || !a.is_evaluated() {
824 return None;
825 }
826 Some(match a.dtype() {
827 DType::Bool => u64::from(a.item::<bool>().ok()?),
828 DType::U8 => u64::from(a.item::<u8>().ok()?),
829 DType::U16 => u64::from(a.item::<u16>().ok()?),
830 DType::U32 => u64::from(a.item::<u32>().ok()?),
831 DType::U64 => a.item::<u64>().ok()?,
832 DType::I8 => a.item::<i8>().ok()? as u64,
833 DType::I16 => a.item::<i16>().ok()? as u64,
834 DType::I32 => a.item::<i32>().ok()? as u64,
835 DType::I64 => a.item::<i64>().ok()? as u64,
836 DType::F16 => u64::from(a.item::<f16>().ok()?.to_bits()),
837 DType::BF16 => u64::from(a.item::<bf16>().ok()?.to_bits()),
838 DType::F32 => u64::from(a.item::<f32>().ok()?.to_bits()),
839 DType::F64 => a.item::<f64>().ok()?.to_bits(),
840 DType::Complex64 => {
841 let z = a.item::<Complex64>().ok()?;
842 u64::from(z.re.to_bits()) << 32 | u64::from(z.im.to_bits())
843 }
844 _ => return None,
845 })
846}
847
848#[cfg(test)]
849mod tests {
850 use nuvec_core::{Device, array, set_default_stream};
851
852 use super::*;
853
854 fn trace(f: &dyn Fn(&[Array]) -> Result<Vec<Array>>, inputs: &[Array], device: Device) -> Tape {
855 Tape::trace(
856 &mut |xs: &[Array]| f(xs),
857 inputs,
858 Stream::default_for(device),
859 )
860 .unwrap()
861 }
862
863 fn names(tape: &Tape) -> Vec<&'static str> {
864 tape.steps.iter().map(|s| s.primitive.name()).collect()
865 }
866
867 #[test]
868 fn version_is_set() {
869 assert!(!VERSION.is_empty());
870 }
871
872 #[test]
873 fn simplifies_identities_constants_and_common_subexpressions() {
874 set_default_stream(Stream::default_for(Device::cpu()));
875 let x = array![1.0, 2.0];
876 let f = |xs: &[Array]| {
877 let x = xs[0].astype(DType::F32).reshape([2]);
878 let a = x.multiply(2.0); let b = x.multiply(2.0); Ok(vec![a.add(&b), x.exp(), x.exp()])
881 };
882 let tape = trace(&f, std::slice::from_ref(&x), Device::cpu());
883 let mut ops = names(&tape);
884 ops.sort_unstable();
885 assert_eq!(ops, ["add", "exp", "multiply"]);
886 assert_eq!(tape.outputs[1], tape.outputs[2]);
887 let outs = tape.replay(&[x]).unwrap();
888 assert_eq!(outs[0].to_vec::<f32>().unwrap(), [4.0, 8.0]);
889 }
890
891 #[test]
892 fn fuses_element_wise_chains_on_the_gpu_only() {
893 set_default_stream(Stream::default_for(Device::cpu()));
894 let x = array![1.0, 2.0];
895 let w = array![[1.0, 0.0], [0.0, 1.0]];
896 let f = |xs: &[Array]| {
899 let h = xs[0].reshape([1, 2]).matmul(&w);
900 let a = h.multiply(2.0).exp();
901 let b = a.multiply(&a).add(1.0);
902 Ok(vec![a, b.sigmoid()])
903 };
904 assert_eq!(
905 names(&trace(&f, std::slice::from_ref(&x), Device::cpu())),
906 [
907 "reshape", "matmul", "multiply", "exp", "multiply", "add", "sigmoid"
908 ]
909 );
910 let tape = trace(&f, &[x], Device::gpu(0));
911 assert_eq!(names(&tape), ["reshape", "matmul", "compiled"]);
912 let Primitive::Compiled(fused) = &tape.steps[2].primitive else {
913 unreachable!()
914 };
915 assert_eq!((fused.len(), fused.dtypes().len()), (5, 2));
916 assert_eq!(tape.outputs, [Ref::Step(2, 0), Ref::Step(2, 1)]);
917 }
918
919 #[test]
920 fn copies_and_dependencies_are_kept() {
921 set_default_stream(Stream::default_for(Device::cpu()));
922 let x = array![1.0, 2.0];
923 let y = array![3.0, 4.0];
924 let f = |xs: &[Array]| {
925 let copy = graph::from_primitive(Primitive::Copy, vec![xs[0].clone()])?.remove(0);
926 let ordered = graph::from_primitive(
927 Primitive::Depends { n_deps: 1 },
928 vec![xs[0].clone(), xs[1].clone()],
929 )?
930 .remove(0);
931 Ok(vec![copy.add(ordered.astype(DType::F32))])
933 };
934 let tape = trace(&f, &[x.clone(), y.clone()], Device::cpu());
935 let mut ops = names(&tape);
936 ops.sort_unstable();
937 assert_eq!(ops, ["add", "copy", "depends"]);
938 let out = tape.replay(&[x, y]).unwrap().remove(0);
939 assert_eq!(out.to_vec::<f32>().unwrap(), [2.0, 4.0]);
940 }
941
942 #[test]
943 fn a_shared_value_used_outside_a_group_stays_outside() {
944 let x = array![1.0, 2.0];
945 let f = |xs: &[Array]| {
948 let t = xs[0].exp();
949 let s = t.sum_keepdims(..);
950 Ok(vec![t.multiply(3.0).add(&s)])
951 };
952 let tape = trace(&f, &[x], Device::gpu(0));
953 assert_eq!(names(&tape), ["exp", "sum", "broadcast_to", "compiled"]);
954 }
955}