1use std::ops::{Add, Div, Mul, Neg, Sub};
7
8use rlx_ir::op::{Activation, BinaryOp, CmpOp, MaskKind, ReduceOp};
9use rlx_ir::{DType, Dim, GraphExt, NodeId, Op, Shape};
10
11use crate::handle::GraphHandle;
12use crate::scalar::{Scalar, promote_scalar};
13use crate::slice::SliceSpec;
14
15macro_rules! unary_ops {
19 ($( $(#[$doc:meta])* $name:ident => $variant:ident ),+ $(,)?) => {
20 impl Tensor {
21 $(
22 $(#[$doc])*
23 pub fn $name(&self) -> Self {
24 self.map_act(Activation::$variant)
25 }
26 )+
27 }
28 };
29}
30
31macro_rules! cmp_ops {
33 ($( $(#[$doc:meta])* $name:ident => $variant:ident ),+ $(,)?) => {
34 impl Tensor {
35 $(
36 $(#[$doc])*
37 pub fn $name(&self, rhs: &Tensor) -> Self {
38 self.map_cmp(BinaryRhs::Tensor(rhs), CmpOp::$variant)
39 }
40 )+
41 }
42 };
43}
44
45macro_rules! reduce_ops {
47 ($( $(#[$doc:meta])* $name:ident => $variant:ident ),+ $(,)?) => {
48 impl Tensor {
49 $(
50 $(#[$doc])*
51 pub fn $name(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
52 self.map_reduce(ReduceOp::$variant, axes.into(), keep_dim)
53 }
54 )+
55 }
56 };
57}
58
59fn dim_to_i64(d: &Dim) -> i64 {
61 match d {
62 Dim::Static(n) => *n as i64,
63 Dim::Dynamic(_) => -1,
64 }
65}
66
67#[cfg(feature = "eval")]
69fn fmt_nested(data: &[f32], dims: &[usize]) -> String {
70 match dims {
71 [] => format!("{}", data.first().copied().unwrap_or(0.0)),
72 [_] => {
73 let body = data
74 .iter()
75 .map(|x| format!("{x}"))
76 .collect::<Vec<_>>()
77 .join(", ");
78 format!("[{body}]")
79 }
80 [outer, rest @ ..] => {
81 let inner: usize = rest.iter().product();
82 let parts = (0..*outer)
83 .map(|i| fmt_nested(&data[i * inner..(i + 1) * inner], rest))
84 .collect::<Vec<_>>()
85 .join(", ");
86 format!("[{parts}]")
87 }
88 }
89}
90
91#[cfg(feature = "eval")]
92impl std::fmt::Display for Tensor {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.write_str(&self.show())
97 }
98}
99
100#[derive(Clone, Debug)]
119pub struct Tensor {
120 pub(crate) handle: GraphHandle,
121 pub(crate) id: NodeId,
122}
123
124impl Tensor {
125 pub(crate) fn new(handle: GraphHandle, id: NodeId) -> Self {
126 Self { handle, id }
127 }
128
129 pub fn id(&self) -> NodeId {
131 self.id
132 }
133
134 pub fn shape(&self) -> Shape {
135 self.handle.with_graph(|g| g.shape(self.id).clone())
136 }
137
138 pub fn dtype(&self) -> DType {
139 self.shape().dtype()
140 }
141
142 pub fn rank(&self) -> usize {
143 self.shape().rank()
144 }
145
146 pub fn dims(&self) -> Vec<usize> {
148 self.shape()
149 .dims()
150 .iter()
151 .map(|d| match d {
152 Dim::Static(n) => *n,
153 Dim::Dynamic(_) => panic!("dims(): tensor has a dynamic axis"),
154 })
155 .collect()
156 }
157
158 pub fn numel(&self) -> usize {
160 self.dims().iter().product()
161 }
162
163 pub fn is_static_shape(&self) -> bool {
165 self.shape().is_static()
166 }
167
168 pub fn shares_graph(&self, other: &Tensor) -> bool {
173 self.handle.same(&other.handle)
174 }
175
176 pub fn storage_bytes(&self) -> usize {
181 self.handle.with_graph(|g| {
182 g.nodes()
183 .iter()
184 .filter_map(|n| match &n.op {
185 Op::Constant { data } => Some(data.len()),
186 _ => None,
187 })
188 .sum()
189 })
190 }
191
192 pub fn slice(&self, spec: SliceSpec) -> Self {
194 spec.apply(self)
195 }
196
197 fn map_unary(&self, f: impl FnOnce(&mut rlx_ir::Graph, NodeId) -> NodeId) -> Self {
198 let id = self.handle.with_graph(|g| f(g, self.id));
199 Self::new(self.handle.clone(), id)
200 }
201
202 pub(crate) fn adopt(&self, other: &Tensor) -> NodeId {
205 self.handle.adopt(&other.handle, other.id)
206 }
207
208 fn map_binary(
209 &self,
210 rhs: BinaryRhs<'_>,
211 f: impl FnOnce(&mut rlx_ir::Graph, NodeId, NodeId) -> NodeId,
212 ) -> Self {
213 let rhs_id = match rhs {
214 BinaryRhs::Tensor(v) => self.adopt(v),
215 BinaryRhs::Scalar(s) => self.handle.with_graph(|g| {
216 let dtype = g.shape(self.id).dtype();
217 promote_scalar(g, s, dtype)
218 }),
219 };
220 let id = self.handle.with_graph(|g| f(g, self.id, rhs_id));
221 Self::new(self.handle.clone(), id)
222 }
223
224 fn map_act(&self, act: Activation) -> Self {
226 self.map_unary(|g, x| {
227 let s = rlx_ir::shape::unary_shape(g.shape(x));
228 g.activation(act, x, s)
229 })
230 }
231
232 fn map_binary_op(&self, rhs: BinaryRhs<'_>, op: BinaryOp) -> Self {
234 self.map_binary(rhs, |g, a, b| {
235 let s = rlx_ir::shape::binary_shape(g.shape(a), g.shape(b))
236 .expect("binary shape inference");
237 g.binary(op, a, b, s)
238 })
239 }
240
241 fn map_cmp(&self, rhs: BinaryRhs<'_>, op: CmpOp) -> Self {
243 self.map_binary(rhs, |g, a, b| {
244 let s = rlx_ir::shape::compare_shape(g.shape(a), g.shape(b))
245 .expect("compare shape inference");
246 g.add_node(Op::Compare(op), vec![a, b], s)
247 })
248 }
249
250 fn map_reduce(&self, op: ReduceOp, axes: Vec<usize>, keep_dim: bool) -> Self {
252 self.map_unary(|g, x| {
253 let s = rlx_ir::shape::reduce_shape(g.shape(x), &axes, keep_dim)
254 .expect("reduce shape inference");
255 g.reduce(x, op, axes, keep_dim, s)
256 })
257 }
258
259 #[doc(alias = "@")]
261 pub fn mm(&self, rhs: &Tensor) -> Self {
262 self.matmul(rhs)
263 }
264
265 pub fn matmul(&self, rhs: &Tensor) -> Self {
266 self.map_binary(BinaryRhs::Tensor(rhs), |g, a, b| g.mm(a, b))
267 }
268
269 pub fn softmax(&self, axis: i32) -> Self {
270 self.map_unary(|g, x| g.sm(x, axis))
271 }
272
273 pub fn reshape(&self, new_shape: impl Into<Vec<i64>>) -> Self {
274 self.map_unary(|g, x| g.reshape_(x, new_shape.into()))
275 }
276
277 pub fn transpose(&self, perm: impl Into<Vec<usize>>) -> Self {
278 self.map_unary(|g, x| g.transpose_(x, perm.into()))
279 }
280
281 pub fn t(&self) -> Self {
282 let rank = self.rank();
283 assert!(rank >= 2, "t() requires rank >= 2");
284 let perm: Vec<usize> = (0..rank - 2).chain([rank - 1, rank - 2]).collect();
285 self.transpose(perm)
286 }
287
288 pub fn cast(&self, to: DType) -> Self {
289 self.map_unary(|g, x| g.cast(x, to))
290 }
291
292 pub fn narrow(&self, axis: usize, start: usize, len: usize) -> Self {
293 self.map_unary(|g, x| g.narrow_(x, axis, start, len))
294 }
295
296 pub fn broadcast_to(&self, shape: impl Into<Vec<i64>>) -> Self {
300 let target = shape.into();
301 self.map_unary(|g, x| {
302 let s = rlx_ir::shape::expand_shape(g.shape(x), &target)
303 .expect("broadcast_to shape inference");
304 g.add_node(
305 Op::Expand {
306 target_shape: target,
307 },
308 vec![x],
309 s,
310 )
311 })
312 }
313
314 pub fn flatten(&self) -> Self {
316 self.reshape(vec![-1_i64])
317 }
318
319 pub fn unsqueeze(&self, axis: usize) -> Self {
322 let mut dims = self.dims_i64();
323 assert!(axis <= dims.len(), "unsqueeze: axis {axis} out of range");
324 dims.insert(axis, 1);
325 self.reshape(dims)
326 }
327
328 pub fn squeeze(&self, axis: usize) -> Self {
330 let dims = self.shape();
331 let dims = dims.dims();
332 assert!(
333 matches!(dims.get(axis), Some(Dim::Static(1))),
334 "squeeze: axis {axis} is not size 1"
335 );
336 let new: Vec<i64> = dims
337 .iter()
338 .enumerate()
339 .filter(|(i, _)| *i != axis)
340 .map(|(_, d)| dim_to_i64(d))
341 .collect();
342 self.reshape(new)
343 }
344
345 pub fn squeeze_all(&self) -> Self {
347 let new: Vec<i64> = self
348 .shape()
349 .dims()
350 .iter()
351 .filter(|d| !matches!(d, Dim::Static(1)))
352 .map(dim_to_i64)
353 .collect();
354 self.reshape(new)
355 }
356
357 fn dims_i64(&self) -> Vec<i64> {
359 self.shape().dims().iter().map(dim_to_i64).collect()
360 }
361
362 pub fn split(&self, axis: usize, sizes: &[usize]) -> Vec<Tensor> {
365 let mut out = Vec::with_capacity(sizes.len());
366 let mut start = 0;
367 for &len in sizes {
368 out.push(self.narrow(axis, start, len));
369 start += len;
370 }
371 out
372 }
373
374 pub fn chunk(&self, axis: usize, n: usize) -> Vec<Tensor> {
376 assert!(n >= 1, "chunk: n must be >= 1");
377 let total = self.dims()[axis];
378 let base = total / n;
379 let rem = total % n;
380 let sizes: Vec<usize> = (0..n).map(|i| base + usize::from(i < rem)).collect();
381 self.split(axis, &sizes)
382 }
383
384 pub fn tile(&self, axis: usize, reps: usize) -> Tensor {
387 assert!(reps >= 1, "tile: reps must be >= 1");
388 let copies = vec![self; reps];
389 crate::array::cat(&copies, axis)
390 }
391
392 pub fn flip(&self, axis: usize) -> Tensor {
394 let n = self.dims()[axis];
395 let rev: Vec<i64> = (0..n as i64).rev().collect();
396 self.index_select(axis, &Tensor::index_vec(rev))
397 }
398
399 pub fn roll(&self, axis: usize, shift: usize) -> Tensor {
401 let n = self.dims()[axis];
402 let s = if n == 0 { 0 } else { shift % n };
403 if s == 0 {
404 return self.clone();
405 }
406 let tail = self.narrow(axis, n - s, s);
407 let head = self.narrow(axis, 0, n - s);
408 crate::array::cat(&[&tail, &head], axis)
409 }
410
411 pub fn pad(&self, axis: usize, before: usize, after: usize, value: f32) -> Tensor {
413 let mut parts: Vec<Tensor> = Vec::new();
414 if before > 0 {
415 let mut d = self.dims();
416 d[axis] = before;
417 parts.push(Tensor::full(&d, value));
418 }
419 parts.push(self.clone());
420 if after > 0 {
421 let mut d = self.dims();
422 d[axis] = after;
423 parts.push(Tensor::full(&d, value));
424 }
425 let refs: Vec<&Tensor> = parts.iter().collect();
426 crate::array::cat(&refs, axis)
427 }
428
429 pub fn gather(&self, indices: &Tensor, axis: usize) -> Self {
430 self.map_binary(BinaryRhs::Tensor(indices), |g, table, idx| {
431 g.gather_(table, idx, axis)
432 })
433 }
434
435 pub fn index_select(&self, axis: usize, indices: &Tensor) -> Self {
438 self.gather(indices, axis)
439 }
440
441 pub fn select(&self, axis: usize, index: i64) -> Self {
444 let dim = match self.shape().dims().get(axis) {
445 Some(Dim::Static(n)) => Some(*n),
446 _ => None,
447 };
448 let pos = crate::slice::resolve_index(index, dim, axis);
449 self.narrow(axis, pos, 1).squeeze(axis)
450 }
451
452 pub fn stop_gradient(&self) -> Self {
453 self.map_unary(|g, x| g.stop_gradient(x))
454 }
455
456 pub fn argmax(&self, axis: usize, keep_dim: bool) -> Self {
459 self.map_unary(|g, x| {
460 let s = rlx_ir::shape::reduce_shape(g.shape(x), &[axis], keep_dim)
461 .expect("argmax shape inference");
462 g.argmax(x, axis, keep_dim, s)
463 })
464 }
465
466 pub fn argmin(&self, axis: usize, keep_dim: bool) -> Self {
468 self.map_unary(|g, x| {
469 let s = rlx_ir::shape::reduce_shape(g.shape(x), &[axis], keep_dim)
470 .expect("argmin shape inference");
471 g.argmin(x, axis, keep_dim, s)
472 })
473 }
474
475 pub fn inv(&self) -> Self {
479 let dims = self.dims();
480 assert!(
481 dims.len() == 2 && dims[0] == dims[1],
482 "inv() requires a square 2-D matrix, got {dims:?}"
483 );
484 let n = dims[0];
485 let eye_id = self.adopt(&Tensor::eye(n));
486 let id = self.handle.with_graph(|g| {
487 let s = Shape::new(&[n, n], DType::F32);
488 g.dense_solve(self.id, eye_id, s)
489 });
490 Self::new(self.handle.clone(), id)
491 }
492
493 pub fn solve(&self, b: &Tensor) -> Self {
496 let s = b.shape();
497 let b_id = self.adopt(b);
498 let id = self.handle.with_graph(|g| g.dense_solve(self.id, b_id, s));
499 Self::new(self.handle.clone(), id)
500 }
501
502 pub fn cumsum(&self, axis: i32, exclusive: bool) -> Self {
504 self.map_unary(|g, x| {
505 let s = rlx_ir::shape::unary_shape(g.shape(x));
506 g.cumsum(x, axis, exclusive, s)
507 })
508 }
509
510 pub fn var(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
512 let axes = axes.into();
513 let mu = self.mean(axes.clone(), true);
514 let centered = self - μ
515 (¢ered * ¢ered).mean(axes, keep_dim)
516 }
517
518 pub fn std(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
520 self.var(axes, keep_dim).sqrt()
521 }
522
523 pub fn norm(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
525 (self * self).sum(axes, keep_dim).sqrt()
526 }
527
528 pub fn logsumexp(&self, axis: usize, keep_dim: bool) -> Self {
531 let m = self.max([axis], true);
532 let shifted = self - &m;
533 let summed = shifted.exp().sum([axis], true);
534 let lse = &summed.log() + &m;
535 if keep_dim { lse } else { lse.squeeze(axis) }
536 }
537
538 pub fn fft(&self) -> Self {
542 self.map_unary(|g, x| g.fft(x, false))
543 }
544
545 pub fn ifft(&self) -> Self {
548 self.map_unary(|g, x| g.fft(x, true))
549 }
550
551 pub fn fft_axis(&self, axis: usize, inverse: bool) -> Self {
553 self.map_unary(|g, x| g.fft_axis(x, axis, inverse))
554 }
555
556 pub fn layer_norm(&self, gamma: &Tensor, beta: &Tensor, eps: f32) -> Self {
557 let g_id = self.adopt(gamma);
558 let b_id = self.adopt(beta);
559 let id = self.handle.with_graph(|g| {
560 let s = rlx_ir::shape::unary_shape(g.shape(self.id));
561 g.layer_norm(self.id, g_id, b_id, -1, eps, s)
562 });
563 Self::new(self.handle.clone(), id)
564 }
565
566 pub fn rms_norm(&self, gamma: &Tensor, beta: &Tensor, eps: f32) -> Self {
567 let g_id = self.adopt(gamma);
568 let b_id = self.adopt(beta);
569 let id = self
570 .handle
571 .with_graph(|g| g.rms_norm(self.id, g_id, b_id, eps));
572 Self::new(self.handle.clone(), id)
573 }
574
575 pub fn conv2d(
576 &self,
577 weight: &Tensor,
578 kernel_size: [usize; 2],
579 stride: [usize; 2],
580 padding: [usize; 2],
581 dilation: [usize; 2],
582 groups: usize,
583 ) -> Self {
584 let w_id = self.adopt(weight);
585 let id = self.handle.with_graph(|g| {
586 g.conv2d(
587 self.id,
588 w_id,
589 kernel_size,
590 stride,
591 padding,
592 dilation,
593 groups,
594 )
595 });
596 Self::new(self.handle.clone(), id)
597 }
598
599 pub fn attention(
600 &self,
601 k: &Tensor,
602 v: &Tensor,
603 num_heads: usize,
604 head_dim: usize,
605 mask_kind: MaskKind,
606 ) -> Self {
607 let k_id = self.adopt(k);
608 let v_id = self.adopt(v);
609 let id = self.handle.with_graph(|g| {
610 let s = rlx_ir::shape::attention_shape(g.shape(self.id));
611 g.attention_kind(self.id, k_id, v_id, num_heads, head_dim, mask_kind, s)
612 });
613 Self::new(self.handle.clone(), id)
614 }
615
616 pub fn rope(&self, cos: &Tensor, sin: &Tensor, head_dim: usize) -> Self {
617 let cos_id = self.adopt(cos);
618 let sin_id = self.adopt(sin);
619 let id = self
620 .handle
621 .with_graph(|g| g.rope(self.id, cos_id, sin_id, head_dim));
622 Self::new(self.handle.clone(), id)
623 }
624
625 pub fn where_(&self, on_true: &Tensor, on_false: &Tensor) -> Self {
626 let t_id = self.adopt(on_true);
627 let f_id = self.adopt(on_false);
628 let id = self.handle.with_graph(|g| {
629 let s = rlx_ir::shape::binary_shape(g.shape(t_id), g.shape(f_id))
630 .expect("where shape inference");
631 g.add_node(Op::Where, vec![self.id, t_id, f_id], s)
632 });
633 Self::new(self.handle.clone(), id)
634 }
635
636 pub fn masked_fill(&self, mask: &Tensor, value: f64) -> Self {
640 let dims: Vec<usize> = self
641 .shape()
642 .dims()
643 .iter()
644 .map(|d| match d {
645 Dim::Static(n) => *n,
646 Dim::Dynamic(_) => panic!("masked_fill requires a static shape"),
647 })
648 .collect();
649 let fill = Tensor::full(dims, value as f32);
650 mask.where_(&fill, self)
651 }
652
653 pub fn maximum(&self, rhs: &Tensor) -> Self {
655 self.map_binary_op(BinaryRhs::Tensor(rhs), BinaryOp::Max)
656 }
657
658 pub fn minimum(&self, rhs: &Tensor) -> Self {
660 self.map_binary_op(BinaryRhs::Tensor(rhs), BinaryOp::Min)
661 }
662
663 pub fn clamp(&self, min: f64, max: f64) -> Self {
665 self.clamp_min(min).clamp_max(max)
666 }
667
668 pub fn clamp_min(&self, min: f64) -> Self {
670 self.map_binary_op(BinaryRhs::Scalar(min.into()), BinaryOp::Max)
671 }
672
673 pub fn clamp_max(&self, max: f64) -> Self {
675 self.map_binary_op(BinaryRhs::Scalar(max.into()), BinaryOp::Min)
676 }
677
678 pub fn pow(&self, rhs: &Tensor) -> Self {
679 self.map_binary_op(BinaryRhs::Tensor(rhs), BinaryOp::Pow)
680 }
681
682 pub fn pow_scalar(&self, exp: f64) -> Self {
683 self.map_binary_op(BinaryRhs::Scalar(exp.into()), BinaryOp::Pow)
684 }
685}
686
687unary_ops! {
688 relu => Relu,
690 gelu => Gelu,
692 gelu_approx => GeluApprox,
694 silu => Silu,
696 tanh => Tanh,
698 exp => Exp,
700 sqrt => Sqrt,
702 sigmoid => Sigmoid,
704 log => Log,
706 rsqrt => Rsqrt,
708 abs => Abs,
710 sin => Sin,
712 cos => Cos,
714 tan => Tan,
716 atan => Atan,
718 round => Round,
720}
721
722cmp_ops! {
723 eq => Eq,
725 ne => Ne,
727 lt => Lt,
729 le => Le,
731 gt => Gt,
733 ge => Ge,
735}
736
737reduce_ops! {
738 sum => Sum,
740 mean => Mean,
742 max => Max,
744 min => Min,
746 prod => Prod,
748}
749
750#[cfg(feature = "eval")]
754macro_rules! pod_readers {
755 ($($(#[$m:meta])* $name:ident => ($ty:ty, $dt:ident, $width:literal)),+ $(,)?) => {
756 $(
757 $(#[$m])*
758 pub fn $name(&self) -> Vec<$ty> {
759 let (bytes, dt) = self.eval_typed();
760 assert_eq!(
761 dt, DType::$dt,
762 concat!(stringify!($name), ": tensor dtype is {:?}"), dt,
763 );
764 bytes
765 .chunks_exact($width)
766 .map(|c| <$ty>::from_le_bytes(c.try_into().unwrap()))
767 .collect()
768 }
769 )+
770 };
771}
772
773#[cfg(feature = "eval")]
778impl Tensor {
779 pub fn to_vec(&self) -> Vec<f32> {
783 self.to_vec_on(self.auto_device())
784 }
785
786 pub fn to_vec_on(&self, device: rlx_runtime::Device) -> Vec<f32> {
788 let compiled = self.compiled_on(device);
789 let mut outputs = compiled.borrow_mut().run(&[]);
790 outputs.pop().expect("eval produced no output")
791 }
792
793 fn auto_device(&self) -> rlx_runtime::Device {
795 self.handle
796 .with_graph(|g| rlx_runtime::fastest_device_for(g))
797 }
798
799 fn compiled_on(
802 &self,
803 device: rlx_runtime::Device,
804 ) -> std::rc::Rc<std::cell::RefCell<rlx_runtime::CompiledGraph>> {
805 self.handle
806 .with_graph(|g| crate::cache::compiled_output(g, self.id, device))
807 }
808
809 pub fn realize(&self) -> Vec<f32> {
815 self.to_vec()
816 }
817
818 pub fn realize_on(&self, device: rlx_runtime::Device) -> Vec<f32> {
820 self.to_vec_on(device)
821 }
822
823 pub fn item(&self) -> f32 {
825 let v = self.to_vec();
826 assert_eq!(v.len(), 1, "item(): tensor has {} elements, not 1", v.len());
827 v[0]
828 }
829
830 pod_readers! {
831 to_vec_f64 => (f64, F64, 8),
833 to_vec_i64 => (i64, I64, 8),
835 to_vec_i32 => (i32, I32, 4),
837 }
838
839 pub fn to_vec_bool(&self) -> Vec<bool> {
843 let (bytes, dt) = self.eval_typed();
844 assert_eq!(dt, DType::Bool, "to_vec_bool: tensor dtype is {dt:?}");
845 let n = self.numel();
846 assert!(n > 0 && bytes.len() % n == 0, "to_vec_bool: ragged output");
847 let w = bytes.len() / n;
848 bytes
849 .chunks_exact(w)
850 .map(|c| c.iter().any(|&b| b != 0))
851 .collect()
852 }
853
854 fn eval_typed(&self) -> (Vec<u8>, DType) {
857 let compiled = self.compiled_on(self.auto_device());
858 let mut outputs = compiled.borrow_mut().run_typed(&[]);
859 outputs.pop().expect("eval produced no output")
860 }
861
862 pub fn show(&self) -> String {
865 let dims = self.dims();
866 let data = self.to_vec();
867 format!(
868 "Tensor{dims:?} {:?}\n{}",
869 self.dtype(),
870 fmt_nested(&data, &dims)
871 )
872 }
873
874 pub fn on(&self, device: rlx_runtime::Device) -> Materialize<'_> {
876 Materialize {
877 tensor: self,
878 device,
879 }
880 }
881}
882
883#[cfg(feature = "eval")]
885pub struct Materialize<'a> {
886 tensor: &'a Tensor,
887 device: rlx_runtime::Device,
888}
889
890#[cfg(feature = "eval")]
891impl Materialize<'_> {
892 pub fn to_vec(self) -> Vec<f32> {
894 self.tensor.to_vec_on(self.device)
895 }
896}
897
898#[cfg(feature = "autodiff")]
901impl Tensor {
902 pub fn grad(&self, wrt: &[&Tensor]) -> Vec<Tensor> {
916 let wrt_ids: Vec<NodeId> = wrt.iter().map(|t| self.adopt(t)).collect();
920 let loss_id = self.id;
921 let bwd = self.handle.with_graph(|g| {
922 let mut forward = g.clone();
923 forward.set_outputs(vec![loss_id]);
924 let mut bwd = rlx_autodiff::grad(&forward, &wrt_ids);
925 bake_unit_seed(&mut bwd);
926 bwd
927 });
928 let handle = GraphHandle::new(bwd);
929 let outputs = handle.with_graph(|g| g.outputs.clone());
930 outputs
931 .into_iter()
932 .map(|id| Tensor::new(handle.clone(), id))
933 .collect()
934 }
935}
936
937#[cfg(feature = "autodiff")]
940pub(crate) fn bake_unit_seed(bwd: &mut rlx_ir::Graph) {
941 let seed = bwd.nodes().iter().find_map(|n| match &n.op {
942 Op::Input { name } if name == "d_output" => Some(n.id),
943 _ => None,
944 });
945 if let Some(id) = seed {
946 let n = bwd.node(id).shape.num_elements().unwrap_or(1);
947 let data: Vec<u8> = (0..n).flat_map(|_| 1.0f32.to_le_bytes()).collect();
948 let node = bwd.node_mut(id);
949 node.op = Op::Constant { data };
950 node.inputs = Vec::new();
951 }
952}
953
954impl From<Tensor> for NodeId {
955 fn from(v: Tensor) -> Self {
956 v.id
957 }
958}
959
960impl From<&Tensor> for NodeId {
961 fn from(v: &Tensor) -> Self {
962 v.id
963 }
964}
965
966pub(crate) enum BinaryRhs<'a> {
967 Tensor(&'a Tensor),
968 Scalar(Scalar),
969}
970
971impl<'a> BinaryRhs<'a> {
972 fn scalar(s: Scalar) -> Self {
973 Self::Scalar(s)
974 }
975}
976
977macro_rules! impl_scalar_rhs {
978 ($($ty:ty),+ $(,)?) => {
979 $(
980 impl From<$ty> for BinaryRhs<'_> {
981 fn from(v: $ty) -> Self {
982 Self::Scalar(v.into())
983 }
984 }
985 )+
986 };
987}
988
989impl_scalar_rhs!(bool, i32, i64, f32, f64);
990
991macro_rules! impl_tensor_binop {
992 ($trait:ident, $method:ident, $graph:ident) => {
993 impl $trait<&Tensor> for &Tensor {
994 type Output = Tensor;
995
996 fn $method(self, rhs: &Tensor) -> Tensor {
997 self.map_binary(BinaryRhs::Tensor(rhs), |g, a, b| g.$graph(a, b))
998 }
999 }
1000
1001 impl $trait<Tensor> for &Tensor {
1002 type Output = Tensor;
1003
1004 fn $method(self, rhs: Tensor) -> Tensor {
1005 self.$method(&rhs)
1006 }
1007 }
1008
1009 impl $trait<f64> for &Tensor {
1010 type Output = Tensor;
1011
1012 fn $method(self, rhs: f64) -> Tensor {
1013 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1014 }
1015 }
1016
1017 impl $trait<f32> for &Tensor {
1018 type Output = Tensor;
1019
1020 fn $method(self, rhs: f32) -> Tensor {
1021 self.$method(rhs as f64)
1022 }
1023 }
1024
1025 impl $trait<i32> for &Tensor {
1026 type Output = Tensor;
1027
1028 fn $method(self, rhs: i32) -> Tensor {
1029 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1030 }
1031 }
1032
1033 impl $trait<&Tensor> for Tensor {
1034 type Output = Tensor;
1035
1036 fn $method(self, rhs: &Tensor) -> Tensor {
1037 (&self).$method(rhs)
1038 }
1039 }
1040
1041 impl $trait<Tensor> for Tensor {
1042 type Output = Tensor;
1043
1044 fn $method(self, rhs: Tensor) -> Tensor {
1045 (&self).$method(&rhs)
1046 }
1047 }
1048
1049 impl $trait<f64> for Tensor {
1050 type Output = Tensor;
1051
1052 fn $method(self, rhs: f64) -> Tensor {
1053 (&self).$method(rhs)
1054 }
1055 }
1056
1057 impl $trait<f32> for Tensor {
1058 type Output = Tensor;
1059
1060 fn $method(self, rhs: f32) -> Tensor {
1061 (&self).$method(rhs)
1062 }
1063 }
1064
1065 impl $trait<i32> for Tensor {
1066 type Output = Tensor;
1067
1068 fn $method(self, rhs: i32) -> Tensor {
1069 (&self).$method(rhs)
1070 }
1071 }
1072 };
1073}
1074
1075impl_tensor_binop!(Add, add, add);
1076impl_tensor_binop!(Sub, sub, sub);
1077impl_tensor_binop!(Mul, mul, mul);
1078impl_tensor_binop!(Div, div, div);
1079
1080impl Neg for &Tensor {
1081 type Output = Tensor;
1082
1083 fn neg(self) -> Tensor {
1084 self.map_unary(|g, x| g.neg(x))
1085 }
1086}
1087
1088macro_rules! impl_scalar_left {
1089 ($trait:ident, $method:ident, $graph:ident) => {
1090 impl $trait<&Tensor> for f64 {
1091 type Output = Tensor;
1092
1093 fn $method(self, rhs: &Tensor) -> Tensor {
1094 let id = rhs.handle.with_graph(|g| {
1095 let lhs_id = promote_scalar(g, self.into(), g.shape(rhs.id).dtype());
1096 g.$graph(lhs_id, rhs.id)
1097 });
1098 Tensor::new(rhs.handle.clone(), id)
1099 }
1100 }
1101
1102 impl $trait<&Tensor> for f32 {
1103 type Output = Tensor;
1104
1105 fn $method(self, rhs: &Tensor) -> Tensor {
1106 (self as f64).$method(rhs)
1107 }
1108 }
1109
1110 impl $trait<&Tensor> for i32 {
1111 type Output = Tensor;
1112
1113 fn $method(self, rhs: &Tensor) -> Tensor {
1114 (self as i64 as f64).$method(rhs)
1115 }
1116 }
1117 };
1118}
1119
1120impl_scalar_left!(Add, add, add);
1121impl_scalar_left!(Sub, sub, sub);
1122impl_scalar_left!(Mul, mul, mul);
1123impl_scalar_left!(Div, div, div);
1124
1125impl Neg for Tensor {
1126 type Output = Tensor;
1127
1128 fn neg(self) -> Tensor {
1129 (&self).neg()
1130 }
1131}