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 if let Some((bytes, DType::F32)) = self.constant_payload() {
793 return bytes
794 .chunks_exact(4)
795 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
796 .collect();
797 }
798 let compiled = self.compiled_on(device);
799 let mut outputs = compiled.borrow_mut().run(&[]);
800 outputs.pop().expect("eval produced no output")
801 }
802
803 fn constant_payload(&self) -> Option<(Vec<u8>, DType)> {
808 self.handle.with_graph(|g| {
809 let node = g.node(self.id);
810 if let Op::Constant { data } = &node.op {
811 if node.shape.size_bytes() == Some(data.len()) {
812 return Some((data.clone(), node.shape.dtype()));
813 }
814 }
815 None
816 })
817 }
818
819 fn auto_device(&self) -> rlx_runtime::Device {
821 self.handle
822 .with_graph(|g| rlx_runtime::fastest_device_for(g))
823 }
824
825 fn compiled_on(
828 &self,
829 device: rlx_runtime::Device,
830 ) -> std::rc::Rc<std::cell::RefCell<rlx_runtime::CompiledGraph>> {
831 self.handle
832 .with_graph(|g| crate::cache::compiled_output(g, self.id, device))
833 }
834
835 pub fn realize(&self) -> Vec<f32> {
841 self.to_vec()
842 }
843
844 pub fn realize_on(&self, device: rlx_runtime::Device) -> Vec<f32> {
846 self.to_vec_on(device)
847 }
848
849 pub fn item(&self) -> f32 {
851 let v = self.to_vec();
852 assert_eq!(v.len(), 1, "item(): tensor has {} elements, not 1", v.len());
853 v[0]
854 }
855
856 pod_readers! {
857 to_vec_f64 => (f64, F64, 8),
859 to_vec_i64 => (i64, I64, 8),
861 to_vec_i32 => (i32, I32, 4),
863 }
864
865 pub fn to_vec_bool(&self) -> Vec<bool> {
869 let (bytes, dt) = self.eval_typed();
870 assert_eq!(dt, DType::Bool, "to_vec_bool: tensor dtype is {dt:?}");
871 let n = self.numel();
872 assert!(n > 0 && bytes.len() % n == 0, "to_vec_bool: ragged output");
873 let w = bytes.len() / n;
874 bytes
875 .chunks_exact(w)
876 .map(|c| c.iter().any(|&b| b != 0))
877 .collect()
878 }
879
880 fn eval_typed(&self) -> (Vec<u8>, DType) {
883 if let Some(ct) = self.constant_payload() {
885 return ct;
886 }
887 let compiled = self.compiled_on(self.auto_device());
888 let mut outputs = compiled.borrow_mut().run_typed(&[]);
889 outputs.pop().expect("eval produced no output")
890 }
891
892 pub fn show(&self) -> String {
895 let dims = self.dims();
896 let data = self.to_vec();
897 format!(
898 "Tensor{dims:?} {:?}\n{}",
899 self.dtype(),
900 fmt_nested(&data, &dims)
901 )
902 }
903
904 pub fn on(&self, device: rlx_runtime::Device) -> Materialize<'_> {
906 Materialize {
907 tensor: self,
908 device,
909 }
910 }
911}
912
913#[cfg(feature = "eval")]
915pub struct Materialize<'a> {
916 tensor: &'a Tensor,
917 device: rlx_runtime::Device,
918}
919
920#[cfg(feature = "eval")]
921impl Materialize<'_> {
922 pub fn to_vec(self) -> Vec<f32> {
924 self.tensor.to_vec_on(self.device)
925 }
926}
927
928#[cfg(feature = "autodiff")]
931impl Tensor {
932 pub fn grad(&self, wrt: &[&Tensor]) -> Vec<Tensor> {
946 let wrt_ids: Vec<NodeId> = wrt.iter().map(|t| self.adopt(t)).collect();
950 let loss_id = self.id;
951 let bwd = self.handle.with_graph(|g| {
952 let mut forward = g.clone();
953 forward.set_outputs(vec![loss_id]);
954 let mut bwd = rlx_autodiff::grad(&forward, &wrt_ids);
955 bake_unit_seed(&mut bwd);
956 bwd
957 });
958 let handle = GraphHandle::new(bwd);
959 let outputs = handle.with_graph(|g| g.outputs.clone());
960 outputs
961 .into_iter()
962 .map(|id| Tensor::new(handle.clone(), id))
963 .collect()
964 }
965}
966
967#[cfg(feature = "autodiff")]
970pub(crate) fn bake_unit_seed(bwd: &mut rlx_ir::Graph) {
971 let seed = bwd.nodes().iter().find_map(|n| match &n.op {
972 Op::Input { name } if name == "d_output" => Some(n.id),
973 _ => None,
974 });
975 if let Some(id) = seed {
976 let n = bwd.node(id).shape.num_elements().unwrap_or(1);
977 let data: Vec<u8> = (0..n).flat_map(|_| 1.0f32.to_le_bytes()).collect();
978 let node = bwd.node_mut(id);
979 node.op = Op::Constant { data };
980 node.inputs = Vec::new();
981 }
982}
983
984impl From<Tensor> for NodeId {
985 fn from(v: Tensor) -> Self {
986 v.id
987 }
988}
989
990impl From<&Tensor> for NodeId {
991 fn from(v: &Tensor) -> Self {
992 v.id
993 }
994}
995
996pub(crate) enum BinaryRhs<'a> {
997 Tensor(&'a Tensor),
998 Scalar(Scalar),
999}
1000
1001impl<'a> BinaryRhs<'a> {
1002 fn scalar(s: Scalar) -> Self {
1003 Self::Scalar(s)
1004 }
1005}
1006
1007macro_rules! impl_scalar_rhs {
1008 ($($ty:ty),+ $(,)?) => {
1009 $(
1010 impl From<$ty> for BinaryRhs<'_> {
1011 fn from(v: $ty) -> Self {
1012 Self::Scalar(v.into())
1013 }
1014 }
1015 )+
1016 };
1017}
1018
1019impl_scalar_rhs!(bool, i32, i64, f32, f64);
1020
1021macro_rules! impl_tensor_binop {
1022 ($trait:ident, $method:ident, $graph:ident) => {
1023 impl $trait<&Tensor> for &Tensor {
1024 type Output = Tensor;
1025
1026 fn $method(self, rhs: &Tensor) -> Tensor {
1027 self.map_binary(BinaryRhs::Tensor(rhs), |g, a, b| g.$graph(a, b))
1028 }
1029 }
1030
1031 impl $trait<Tensor> for &Tensor {
1032 type Output = Tensor;
1033
1034 fn $method(self, rhs: Tensor) -> Tensor {
1035 self.$method(&rhs)
1036 }
1037 }
1038
1039 impl $trait<f64> for &Tensor {
1040 type Output = Tensor;
1041
1042 fn $method(self, rhs: f64) -> Tensor {
1043 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1044 }
1045 }
1046
1047 impl $trait<f32> for &Tensor {
1048 type Output = Tensor;
1049
1050 fn $method(self, rhs: f32) -> Tensor {
1051 self.$method(rhs as f64)
1052 }
1053 }
1054
1055 impl $trait<i32> for &Tensor {
1056 type Output = Tensor;
1057
1058 fn $method(self, rhs: i32) -> Tensor {
1059 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1060 }
1061 }
1062
1063 impl $trait<&Tensor> for Tensor {
1064 type Output = Tensor;
1065
1066 fn $method(self, rhs: &Tensor) -> Tensor {
1067 (&self).$method(rhs)
1068 }
1069 }
1070
1071 impl $trait<Tensor> for Tensor {
1072 type Output = Tensor;
1073
1074 fn $method(self, rhs: Tensor) -> Tensor {
1075 (&self).$method(&rhs)
1076 }
1077 }
1078
1079 impl $trait<f64> for Tensor {
1080 type Output = Tensor;
1081
1082 fn $method(self, rhs: f64) -> Tensor {
1083 (&self).$method(rhs)
1084 }
1085 }
1086
1087 impl $trait<f32> for Tensor {
1088 type Output = Tensor;
1089
1090 fn $method(self, rhs: f32) -> Tensor {
1091 (&self).$method(rhs)
1092 }
1093 }
1094
1095 impl $trait<i32> for Tensor {
1096 type Output = Tensor;
1097
1098 fn $method(self, rhs: i32) -> Tensor {
1099 (&self).$method(rhs)
1100 }
1101 }
1102 };
1103}
1104
1105impl_tensor_binop!(Add, add, add);
1106impl_tensor_binop!(Sub, sub, sub);
1107impl_tensor_binop!(Mul, mul, mul);
1108impl_tensor_binop!(Div, div, div);
1109
1110impl Neg for &Tensor {
1111 type Output = Tensor;
1112
1113 fn neg(self) -> Tensor {
1114 self.map_unary(|g, x| g.neg(x))
1115 }
1116}
1117
1118macro_rules! impl_scalar_left {
1119 ($trait:ident, $method:ident, $graph:ident) => {
1120 impl $trait<&Tensor> for f64 {
1121 type Output = Tensor;
1122
1123 fn $method(self, rhs: &Tensor) -> Tensor {
1124 let id = rhs.handle.with_graph(|g| {
1125 let lhs_id = promote_scalar(g, self.into(), g.shape(rhs.id).dtype());
1126 g.$graph(lhs_id, rhs.id)
1127 });
1128 Tensor::new(rhs.handle.clone(), id)
1129 }
1130 }
1131
1132 impl $trait<&Tensor> for f32 {
1133 type Output = Tensor;
1134
1135 fn $method(self, rhs: &Tensor) -> Tensor {
1136 (self as f64).$method(rhs)
1137 }
1138 }
1139
1140 impl $trait<&Tensor> for i32 {
1141 type Output = Tensor;
1142
1143 fn $method(self, rhs: &Tensor) -> Tensor {
1144 (self as i64 as f64).$method(rhs)
1145 }
1146 }
1147 };
1148}
1149
1150impl_scalar_left!(Add, add, add);
1151impl_scalar_left!(Sub, sub, sub);
1152impl_scalar_left!(Mul, mul, mul);
1153impl_scalar_left!(Div, div, div);
1154
1155impl Neg for Tensor {
1156 type Output = Tensor;
1157
1158 fn neg(self) -> Tensor {
1159 (&self).neg()
1160 }
1161}