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, ScaleLayout, ScaledFormat, 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 scaled_matmul(&self, rhs: &Tensor, fmt: ScaledFormat, layout: ScaleLayout) -> Self {
276 self.map_binary(BinaryRhs::Tensor(rhs), move |g, a, b| {
277 g.scaled_matmul(a, b, fmt, layout)
278 })
279 }
280
281 pub fn softmax(&self, axis: i32) -> Self {
282 self.map_unary(|g, x| g.sm(x, axis))
283 }
284
285 pub fn reshape(&self, new_shape: impl Into<Vec<i64>>) -> Self {
286 self.map_unary(|g, x| g.reshape_(x, new_shape.into()))
287 }
288
289 pub fn transpose(&self, perm: impl Into<Vec<usize>>) -> Self {
290 self.map_unary(|g, x| g.transpose_(x, perm.into()))
291 }
292
293 pub fn t(&self) -> Self {
294 let rank = self.rank();
295 assert!(rank >= 2, "t() requires rank >= 2");
296 let perm: Vec<usize> = (0..rank - 2).chain([rank - 1, rank - 2]).collect();
297 self.transpose(perm)
298 }
299
300 pub fn cast(&self, to: DType) -> Self {
301 self.map_unary(|g, x| g.cast(x, to))
302 }
303
304 pub fn narrow(&self, axis: usize, start: usize, len: usize) -> Self {
305 self.map_unary(|g, x| g.narrow_(x, axis, start, len))
306 }
307
308 pub fn broadcast_to(&self, shape: impl Into<Vec<i64>>) -> Self {
312 let target = shape.into();
313 self.map_unary(|g, x| {
314 let s = rlx_ir::shape::expand_shape(g.shape(x), &target)
315 .expect("broadcast_to shape inference");
316 g.add_node(
317 Op::Expand {
318 target_shape: target,
319 },
320 vec![x],
321 s,
322 )
323 })
324 }
325
326 pub fn flatten(&self) -> Self {
328 self.reshape(vec![-1_i64])
329 }
330
331 pub fn unsqueeze(&self, axis: usize) -> Self {
334 let mut dims = self.dims_i64();
335 assert!(axis <= dims.len(), "unsqueeze: axis {axis} out of range");
336 dims.insert(axis, 1);
337 self.reshape(dims)
338 }
339
340 pub fn squeeze(&self, axis: usize) -> Self {
342 let dims = self.shape();
343 let dims = dims.dims();
344 assert!(
345 matches!(dims.get(axis), Some(Dim::Static(1))),
346 "squeeze: axis {axis} is not size 1"
347 );
348 let new: Vec<i64> = dims
349 .iter()
350 .enumerate()
351 .filter(|(i, _)| *i != axis)
352 .map(|(_, d)| dim_to_i64(d))
353 .collect();
354 self.reshape(new)
355 }
356
357 pub fn squeeze_all(&self) -> Self {
359 let new: Vec<i64> = self
360 .shape()
361 .dims()
362 .iter()
363 .filter(|d| !matches!(d, Dim::Static(1)))
364 .map(dim_to_i64)
365 .collect();
366 self.reshape(new)
367 }
368
369 fn dims_i64(&self) -> Vec<i64> {
371 self.shape().dims().iter().map(dim_to_i64).collect()
372 }
373
374 pub fn split(&self, axis: usize, sizes: &[usize]) -> Vec<Tensor> {
377 let mut out = Vec::with_capacity(sizes.len());
378 let mut start = 0;
379 for &len in sizes {
380 out.push(self.narrow(axis, start, len));
381 start += len;
382 }
383 out
384 }
385
386 pub fn chunk(&self, axis: usize, n: usize) -> Vec<Tensor> {
388 assert!(n >= 1, "chunk: n must be >= 1");
389 let total = self.dims()[axis];
390 let base = total / n;
391 let rem = total % n;
392 let sizes: Vec<usize> = (0..n).map(|i| base + usize::from(i < rem)).collect();
393 self.split(axis, &sizes)
394 }
395
396 pub fn tile(&self, axis: usize, reps: usize) -> Tensor {
399 assert!(reps >= 1, "tile: reps must be >= 1");
400 let copies = vec![self; reps];
401 crate::array::cat(&copies, axis)
402 }
403
404 pub fn flip(&self, axis: usize) -> Tensor {
406 let n = self.dims()[axis];
407 let rev: Vec<i64> = (0..n as i64).rev().collect();
408 self.index_select(axis, &Tensor::index_vec(rev))
409 }
410
411 pub fn roll(&self, axis: usize, shift: usize) -> Tensor {
413 let n = self.dims()[axis];
414 let s = if n == 0 { 0 } else { shift % n };
415 if s == 0 {
416 return self.clone();
417 }
418 let tail = self.narrow(axis, n - s, s);
419 let head = self.narrow(axis, 0, n - s);
420 crate::array::cat(&[&tail, &head], axis)
421 }
422
423 pub fn pad(&self, axis: usize, before: usize, after: usize, value: f32) -> Tensor {
425 let mut parts: Vec<Tensor> = Vec::new();
426 if before > 0 {
427 let mut d = self.dims();
428 d[axis] = before;
429 parts.push(Tensor::full(&d, value));
430 }
431 parts.push(self.clone());
432 if after > 0 {
433 let mut d = self.dims();
434 d[axis] = after;
435 parts.push(Tensor::full(&d, value));
436 }
437 let refs: Vec<&Tensor> = parts.iter().collect();
438 crate::array::cat(&refs, axis)
439 }
440
441 pub fn gather(&self, indices: &Tensor, axis: usize) -> Self {
442 self.map_binary(BinaryRhs::Tensor(indices), |g, table, idx| {
443 g.gather_(table, idx, axis)
444 })
445 }
446
447 pub fn index_select(&self, axis: usize, indices: &Tensor) -> Self {
450 self.gather(indices, axis)
451 }
452
453 pub fn select(&self, axis: usize, index: i64) -> Self {
456 let dim = match self.shape().dims().get(axis) {
457 Some(Dim::Static(n)) => Some(*n),
458 _ => None,
459 };
460 let pos = crate::slice::resolve_index(index, dim, axis);
461 self.narrow(axis, pos, 1).squeeze(axis)
462 }
463
464 pub fn stop_gradient(&self) -> Self {
465 self.map_unary(|g, x| g.stop_gradient(x))
466 }
467
468 pub fn argmax(&self, axis: usize, keep_dim: bool) -> Self {
471 self.map_unary(|g, x| {
472 let s = rlx_ir::shape::reduce_shape(g.shape(x), &[axis], keep_dim)
473 .expect("argmax shape inference");
474 g.argmax(x, axis, keep_dim, s)
475 })
476 }
477
478 pub fn argmin(&self, axis: usize, keep_dim: bool) -> Self {
480 self.map_unary(|g, x| {
481 let s = rlx_ir::shape::reduce_shape(g.shape(x), &[axis], keep_dim)
482 .expect("argmin shape inference");
483 g.argmin(x, axis, keep_dim, s)
484 })
485 }
486
487 pub fn inv(&self) -> Self {
491 let dims = self.dims();
492 assert!(
493 dims.len() == 2 && dims[0] == dims[1],
494 "inv() requires a square 2-D matrix, got {dims:?}"
495 );
496 let n = dims[0];
497 let eye_id = self.adopt(&Tensor::eye(n));
498 let id = self.handle.with_graph(|g| {
499 let s = Shape::new(&[n, n], DType::F32);
500 g.dense_solve(self.id, eye_id, s)
501 });
502 Self::new(self.handle.clone(), id)
503 }
504
505 pub fn solve(&self, b: &Tensor) -> Self {
508 let s = b.shape();
509 let b_id = self.adopt(b);
510 let id = self.handle.with_graph(|g| g.dense_solve(self.id, b_id, s));
511 Self::new(self.handle.clone(), id)
512 }
513
514 pub fn cumsum(&self, axis: i32, exclusive: bool) -> Self {
516 self.map_unary(|g, x| {
517 let s = rlx_ir::shape::unary_shape(g.shape(x));
518 g.cumsum(x, axis, exclusive, s)
519 })
520 }
521
522 pub fn var(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
524 let axes = axes.into();
525 let mu = self.mean(axes.clone(), true);
526 let centered = self - μ
527 (¢ered * ¢ered).mean(axes, keep_dim)
528 }
529
530 pub fn std(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
532 self.var(axes, keep_dim).sqrt()
533 }
534
535 pub fn norm(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
537 (self * self).sum(axes, keep_dim).sqrt()
538 }
539
540 pub fn logsumexp(&self, axis: usize, keep_dim: bool) -> Self {
543 let m = self.max([axis], true);
544 let shifted = self - &m;
545 let summed = shifted.exp().sum([axis], true);
546 let lse = &summed.log() + &m;
547 if keep_dim { lse } else { lse.squeeze(axis) }
548 }
549
550 pub fn fft(&self) -> Self {
554 self.map_unary(|g, x| g.fft(x, false))
555 }
556
557 pub fn ifft(&self) -> Self {
560 self.map_unary(|g, x| g.fft(x, true))
561 }
562
563 pub fn fft_axis(&self, axis: usize, inverse: bool) -> Self {
565 self.map_unary(|g, x| g.fft_axis(x, axis, inverse))
566 }
567
568 pub fn layer_norm(&self, gamma: &Tensor, beta: &Tensor, eps: f32) -> Self {
569 let g_id = self.adopt(gamma);
570 let b_id = self.adopt(beta);
571 let id = self.handle.with_graph(|g| {
572 let s = rlx_ir::shape::unary_shape(g.shape(self.id));
573 g.layer_norm(self.id, g_id, b_id, -1, eps, s)
574 });
575 Self::new(self.handle.clone(), id)
576 }
577
578 pub fn rms_norm(&self, gamma: &Tensor, beta: &Tensor, eps: f32) -> Self {
579 let g_id = self.adopt(gamma);
580 let b_id = self.adopt(beta);
581 let id = self
582 .handle
583 .with_graph(|g| g.rms_norm(self.id, g_id, b_id, eps));
584 Self::new(self.handle.clone(), id)
585 }
586
587 pub fn conv2d(
588 &self,
589 weight: &Tensor,
590 kernel_size: [usize; 2],
591 stride: [usize; 2],
592 padding: [usize; 2],
593 dilation: [usize; 2],
594 groups: usize,
595 ) -> Self {
596 let w_id = self.adopt(weight);
597 let id = self.handle.with_graph(|g| {
598 g.conv2d(
599 self.id,
600 w_id,
601 kernel_size,
602 stride,
603 padding,
604 dilation,
605 groups,
606 )
607 });
608 Self::new(self.handle.clone(), id)
609 }
610
611 pub fn attention(
612 &self,
613 k: &Tensor,
614 v: &Tensor,
615 num_heads: usize,
616 head_dim: usize,
617 mask_kind: MaskKind,
618 ) -> Self {
619 let k_id = self.adopt(k);
620 let v_id = self.adopt(v);
621 let id = self.handle.with_graph(|g| {
622 let s = rlx_ir::shape::attention_shape(g.shape(self.id));
623 g.attention_kind(self.id, k_id, v_id, num_heads, head_dim, mask_kind, s)
624 });
625 Self::new(self.handle.clone(), id)
626 }
627
628 pub fn rope(&self, cos: &Tensor, sin: &Tensor, head_dim: usize) -> Self {
629 let cos_id = self.adopt(cos);
630 let sin_id = self.adopt(sin);
631 let id = self
632 .handle
633 .with_graph(|g| g.rope(self.id, cos_id, sin_id, head_dim));
634 Self::new(self.handle.clone(), id)
635 }
636
637 pub fn where_(&self, on_true: &Tensor, on_false: &Tensor) -> Self {
638 let t_id = self.adopt(on_true);
639 let f_id = self.adopt(on_false);
640 let id = self.handle.with_graph(|g| {
641 let s = rlx_ir::shape::binary_shape(g.shape(t_id), g.shape(f_id))
642 .expect("where shape inference");
643 g.add_node(Op::Where, vec![self.id, t_id, f_id], s)
644 });
645 Self::new(self.handle.clone(), id)
646 }
647
648 pub fn masked_fill(&self, mask: &Tensor, value: f64) -> Self {
652 let dims: Vec<usize> = self
653 .shape()
654 .dims()
655 .iter()
656 .map(|d| match d {
657 Dim::Static(n) => *n,
658 Dim::Dynamic(_) => panic!("masked_fill requires a static shape"),
659 })
660 .collect();
661 let fill = Tensor::full(dims, value as f32);
662 mask.where_(&fill, self)
663 }
664
665 pub fn maximum(&self, rhs: &Tensor) -> Self {
667 self.map_binary_op(BinaryRhs::Tensor(rhs), BinaryOp::Max)
668 }
669
670 pub fn minimum(&self, rhs: &Tensor) -> Self {
672 self.map_binary_op(BinaryRhs::Tensor(rhs), BinaryOp::Min)
673 }
674
675 pub fn clamp(&self, min: f64, max: f64) -> Self {
677 self.clamp_min(min).clamp_max(max)
678 }
679
680 pub fn clamp_min(&self, min: f64) -> Self {
682 self.map_binary_op(BinaryRhs::Scalar(min.into()), BinaryOp::Max)
683 }
684
685 pub fn clamp_max(&self, max: f64) -> Self {
687 self.map_binary_op(BinaryRhs::Scalar(max.into()), BinaryOp::Min)
688 }
689
690 pub fn pow(&self, rhs: &Tensor) -> Self {
691 self.map_binary_op(BinaryRhs::Tensor(rhs), BinaryOp::Pow)
692 }
693
694 pub fn pow_scalar(&self, exp: f64) -> Self {
695 self.map_binary_op(BinaryRhs::Scalar(exp.into()), BinaryOp::Pow)
696 }
697}
698
699unary_ops! {
700 relu => Relu,
702 gelu => Gelu,
704 gelu_approx => GeluApprox,
706 silu => Silu,
708 tanh => Tanh,
710 exp => Exp,
712 sqrt => Sqrt,
714 sigmoid => Sigmoid,
716 log => Log,
718 rsqrt => Rsqrt,
720 abs => Abs,
722 sin => Sin,
724 cos => Cos,
726 tan => Tan,
728 atan => Atan,
730 round => Round,
732}
733
734cmp_ops! {
735 eq => Eq,
737 ne => Ne,
739 lt => Lt,
741 le => Le,
743 gt => Gt,
745 ge => Ge,
747}
748
749reduce_ops! {
750 sum => Sum,
752 mean => Mean,
754 max => Max,
756 min => Min,
758 prod => Prod,
760}
761
762#[cfg(feature = "eval")]
766macro_rules! pod_readers {
767 ($($(#[$m:meta])* $name:ident => ($ty:ty, $dt:ident, $width:literal)),+ $(,)?) => {
768 $(
769 $(#[$m])*
770 pub fn $name(&self) -> Vec<$ty> {
771 let (bytes, dt) = self.eval_typed();
772 assert_eq!(
773 dt, DType::$dt,
774 concat!(stringify!($name), ": tensor dtype is {:?}"), dt,
775 );
776 bytes
777 .chunks_exact($width)
778 .map(|c| <$ty>::from_le_bytes(c.try_into().unwrap()))
779 .collect()
780 }
781 )+
782 };
783}
784
785#[cfg(feature = "eval")]
790impl Tensor {
791 pub fn to_vec(&self) -> Vec<f32> {
795 self.to_vec_on(self.auto_device())
796 }
797
798 pub fn to_vec_on(&self, device: rlx_runtime::Device) -> Vec<f32> {
800 if let Some((bytes, DType::F32)) = self.constant_payload() {
805 return bytes
806 .chunks_exact(4)
807 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
808 .collect();
809 }
810 let compiled = self.compiled_on(device);
811 let mut outputs = compiled.borrow_mut().run(&[]);
812 outputs.pop().expect("eval produced no output")
813 }
814
815 fn constant_payload(&self) -> Option<(Vec<u8>, DType)> {
820 self.handle.with_graph(|g| {
821 let node = g.node(self.id);
822 if let Op::Constant { data } = &node.op {
823 if node.shape.size_bytes() == Some(data.len()) {
824 return Some((data.clone(), node.shape.dtype()));
825 }
826 }
827 None
828 })
829 }
830
831 fn auto_device(&self) -> rlx_runtime::Device {
833 self.handle
834 .with_graph(|g| rlx_runtime::fastest_device_for(g))
835 }
836
837 fn compiled_on(
840 &self,
841 device: rlx_runtime::Device,
842 ) -> std::rc::Rc<std::cell::RefCell<rlx_runtime::CompiledGraph>> {
843 self.handle
844 .with_graph(|g| crate::cache::compiled_output(g, self.id, device))
845 }
846
847 pub fn realize(&self) -> Vec<f32> {
853 self.to_vec()
854 }
855
856 pub fn realize_on(&self, device: rlx_runtime::Device) -> Vec<f32> {
858 self.to_vec_on(device)
859 }
860
861 pub fn item(&self) -> f32 {
863 let v = self.to_vec();
864 assert_eq!(v.len(), 1, "item(): tensor has {} elements, not 1", v.len());
865 v[0]
866 }
867
868 pod_readers! {
869 to_vec_f64 => (f64, F64, 8),
871 to_vec_i64 => (i64, I64, 8),
873 to_vec_i32 => (i32, I32, 4),
875 }
876
877 pub fn to_vec_bool(&self) -> Vec<bool> {
881 let (bytes, dt) = self.eval_typed();
882 assert_eq!(dt, DType::Bool, "to_vec_bool: tensor dtype is {dt:?}");
883 let n = self.numel();
884 assert!(n > 0 && bytes.len() % n == 0, "to_vec_bool: ragged output");
885 let w = bytes.len() / n;
886 bytes
887 .chunks_exact(w)
888 .map(|c| c.iter().any(|&b| b != 0))
889 .collect()
890 }
891
892 fn eval_typed(&self) -> (Vec<u8>, DType) {
895 if let Some(ct) = self.constant_payload() {
897 return ct;
898 }
899 let compiled = self.compiled_on(self.auto_device());
900 let mut outputs = compiled.borrow_mut().run_typed(&[]);
901 outputs.pop().expect("eval produced no output")
902 }
903
904 pub fn show(&self) -> String {
907 let dims = self.dims();
908 let data = self.to_vec();
909 format!(
910 "Tensor{dims:?} {:?}\n{}",
911 self.dtype(),
912 fmt_nested(&data, &dims)
913 )
914 }
915
916 pub fn on(&self, device: rlx_runtime::Device) -> Materialize<'_> {
918 Materialize {
919 tensor: self,
920 device,
921 }
922 }
923}
924
925#[cfg(feature = "eval")]
927pub struct Materialize<'a> {
928 tensor: &'a Tensor,
929 device: rlx_runtime::Device,
930}
931
932#[cfg(feature = "eval")]
933impl Materialize<'_> {
934 pub fn to_vec(self) -> Vec<f32> {
936 self.tensor.to_vec_on(self.device)
937 }
938}
939
940#[cfg(feature = "autodiff")]
943impl Tensor {
944 pub fn grad(&self, wrt: &[&Tensor]) -> Vec<Tensor> {
958 let wrt_ids: Vec<NodeId> = wrt.iter().map(|t| self.adopt(t)).collect();
962 let loss_id = self.id;
963 let bwd = self.handle.with_graph(|g| {
964 let mut forward = g.clone();
965 forward.set_outputs(vec![loss_id]);
966 let mut bwd = rlx_autodiff::grad(&forward, &wrt_ids);
967 bake_unit_seed(&mut bwd);
968 bwd
969 });
970 let handle = GraphHandle::new(bwd);
971 let outputs = handle.with_graph(|g| g.outputs.clone());
972 outputs
973 .into_iter()
974 .map(|id| Tensor::new(handle.clone(), id))
975 .collect()
976 }
977}
978
979#[cfg(feature = "autodiff")]
982pub(crate) fn bake_unit_seed(bwd: &mut rlx_ir::Graph) {
983 let seed = bwd.nodes().iter().find_map(|n| match &n.op {
984 Op::Input { name } if name == "d_output" => Some(n.id),
985 _ => None,
986 });
987 if let Some(id) = seed {
988 let n = bwd.node(id).shape.num_elements().unwrap_or(1);
989 let data: Vec<u8> = (0..n).flat_map(|_| 1.0f32.to_le_bytes()).collect();
990 let node = bwd.node_mut(id);
991 node.op = Op::Constant { data };
992 node.inputs = Vec::new();
993 }
994}
995
996impl From<Tensor> for NodeId {
997 fn from(v: Tensor) -> Self {
998 v.id
999 }
1000}
1001
1002impl From<&Tensor> for NodeId {
1003 fn from(v: &Tensor) -> Self {
1004 v.id
1005 }
1006}
1007
1008pub(crate) enum BinaryRhs<'a> {
1009 Tensor(&'a Tensor),
1010 Scalar(Scalar),
1011}
1012
1013impl<'a> BinaryRhs<'a> {
1014 fn scalar(s: Scalar) -> Self {
1015 Self::Scalar(s)
1016 }
1017}
1018
1019macro_rules! impl_scalar_rhs {
1020 ($($ty:ty),+ $(,)?) => {
1021 $(
1022 impl From<$ty> for BinaryRhs<'_> {
1023 fn from(v: $ty) -> Self {
1024 Self::Scalar(v.into())
1025 }
1026 }
1027 )+
1028 };
1029}
1030
1031impl_scalar_rhs!(bool, i32, i64, f32, f64);
1032
1033macro_rules! impl_tensor_binop {
1034 ($trait:ident, $method:ident, $graph:ident) => {
1035 impl $trait<&Tensor> for &Tensor {
1036 type Output = Tensor;
1037
1038 fn $method(self, rhs: &Tensor) -> Tensor {
1039 self.map_binary(BinaryRhs::Tensor(rhs), |g, a, b| g.$graph(a, b))
1040 }
1041 }
1042
1043 impl $trait<Tensor> for &Tensor {
1044 type Output = Tensor;
1045
1046 fn $method(self, rhs: Tensor) -> Tensor {
1047 self.$method(&rhs)
1048 }
1049 }
1050
1051 impl $trait<f64> for &Tensor {
1052 type Output = Tensor;
1053
1054 fn $method(self, rhs: f64) -> Tensor {
1055 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1056 }
1057 }
1058
1059 impl $trait<f32> for &Tensor {
1060 type Output = Tensor;
1061
1062 fn $method(self, rhs: f32) -> Tensor {
1063 self.$method(rhs as f64)
1064 }
1065 }
1066
1067 impl $trait<i32> for &Tensor {
1068 type Output = Tensor;
1069
1070 fn $method(self, rhs: i32) -> Tensor {
1071 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1072 }
1073 }
1074
1075 impl $trait<&Tensor> for Tensor {
1076 type Output = Tensor;
1077
1078 fn $method(self, rhs: &Tensor) -> Tensor {
1079 (&self).$method(rhs)
1080 }
1081 }
1082
1083 impl $trait<Tensor> for Tensor {
1084 type Output = Tensor;
1085
1086 fn $method(self, rhs: Tensor) -> Tensor {
1087 (&self).$method(&rhs)
1088 }
1089 }
1090
1091 impl $trait<f64> for Tensor {
1092 type Output = Tensor;
1093
1094 fn $method(self, rhs: f64) -> Tensor {
1095 (&self).$method(rhs)
1096 }
1097 }
1098
1099 impl $trait<f32> for Tensor {
1100 type Output = Tensor;
1101
1102 fn $method(self, rhs: f32) -> Tensor {
1103 (&self).$method(rhs)
1104 }
1105 }
1106
1107 impl $trait<i32> for Tensor {
1108 type Output = Tensor;
1109
1110 fn $method(self, rhs: i32) -> Tensor {
1111 (&self).$method(rhs)
1112 }
1113 }
1114 };
1115}
1116
1117impl_tensor_binop!(Add, add, add);
1118impl_tensor_binop!(Sub, sub, sub);
1119impl_tensor_binop!(Mul, mul, mul);
1120impl_tensor_binop!(Div, div, div);
1121
1122impl Neg for &Tensor {
1123 type Output = Tensor;
1124
1125 fn neg(self) -> Tensor {
1126 self.map_unary(|g, x| g.neg(x))
1127 }
1128}
1129
1130macro_rules! impl_scalar_left {
1131 ($trait:ident, $method:ident, $graph:ident) => {
1132 impl $trait<&Tensor> for f64 {
1133 type Output = Tensor;
1134
1135 fn $method(self, rhs: &Tensor) -> Tensor {
1136 let id = rhs.handle.with_graph(|g| {
1137 let lhs_id = promote_scalar(g, self.into(), g.shape(rhs.id).dtype());
1138 g.$graph(lhs_id, rhs.id)
1139 });
1140 Tensor::new(rhs.handle.clone(), id)
1141 }
1142 }
1143
1144 impl $trait<&Tensor> for f32 {
1145 type Output = Tensor;
1146
1147 fn $method(self, rhs: &Tensor) -> Tensor {
1148 (self as f64).$method(rhs)
1149 }
1150 }
1151
1152 impl $trait<&Tensor> for i32 {
1153 type Output = Tensor;
1154
1155 fn $method(self, rhs: &Tensor) -> Tensor {
1156 (self as i64 as f64).$method(rhs)
1157 }
1158 }
1159 };
1160}
1161
1162impl_scalar_left!(Add, add, add);
1163impl_scalar_left!(Sub, sub, sub);
1164impl_scalar_left!(Mul, mul, mul);
1165impl_scalar_left!(Div, div, div);
1166
1167impl Neg for Tensor {
1168 type Output = Tensor;
1169
1170 fn neg(self) -> Tensor {
1171 (&self).neg()
1172 }
1173}