1#![allow(private_bounds, private_interfaces)]
13
14use std::ops::{Add, Div, Mul, Neg, Sub};
15
16use rlx_ir::op::{Activation, BinaryOp, CmpOp, MaskKind, ReduceOp};
17use rlx_ir::{DType, Dim, GraphExt, NodeId, Op, ScaleLayout, ScaledFormat, Shape};
18
19use crate::handle::GraphHandle;
20use crate::scalar::{Scalar, promote_scalar};
21use crate::slice::SliceSpec;
22
23macro_rules! unary_ops {
27 ($( $(#[$doc:meta])* $name:ident => $variant:ident ),+ $(,)?) => {
28 impl Tensor {
29 $(
30 $(#[$doc])*
31 pub fn $name(&self) -> Self {
32 self.map_act(Activation::$variant)
33 }
34 )+
35 }
36 };
37}
38
39macro_rules! cmp_ops {
43 ($( $(#[$doc:meta])* $name:ident => $variant:ident ),+ $(,)?) => {
44 impl Tensor {
45 $(
46 $(#[$doc])*
47 pub fn $name<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
48 self.map_cmp(rhs.into(), CmpOp::$variant)
49 }
50 )+
51 }
52 };
53}
54
55macro_rules! reduce_ops {
57 ($( $(#[$doc:meta])* $name:ident => $variant:ident ),+ $(,)?) => {
58 impl Tensor {
59 $(
60 $(#[$doc])*
61 pub fn $name(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
62 self.map_reduce(ReduceOp::$variant, axes.into(), keep_dim)
63 }
64 )+
65 }
66 };
67}
68
69fn dim_to_i64(d: &Dim) -> i64 {
71 match d {
72 Dim::Static(n) => *n as i64,
73 Dim::Dynamic(_) => -1,
74 }
75}
76
77#[cfg(feature = "eval")]
79fn fmt_nested(data: &[f32], dims: &[usize]) -> String {
80 match dims {
81 [] => format!("{}", data.first().copied().unwrap_or(0.0)),
82 [_] => {
83 let body = data
84 .iter()
85 .map(|x| format!("{x}"))
86 .collect::<Vec<_>>()
87 .join(", ");
88 format!("[{body}]")
89 }
90 [outer, rest @ ..] => {
91 let inner: usize = rest.iter().product();
92 let parts = (0..*outer)
93 .map(|i| fmt_nested(&data[i * inner..(i + 1) * inner], rest))
94 .collect::<Vec<_>>()
95 .join(", ");
96 format!("[{parts}]")
97 }
98 }
99}
100
101#[cfg(feature = "eval")]
102impl std::fmt::Display for Tensor {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.write_str(&self.show())
107 }
108}
109
110#[derive(Clone, Debug)]
129pub struct Tensor {
130 pub(crate) handle: GraphHandle,
131 pub(crate) id: NodeId,
132}
133
134impl Tensor {
135 pub(crate) fn new(handle: GraphHandle, id: NodeId) -> Self {
136 Self { handle, id }
137 }
138
139 pub fn id(&self) -> NodeId {
141 self.id
142 }
143
144 pub fn shape(&self) -> Shape {
145 self.handle.with_graph(|g| g.shape(self.id).clone())
146 }
147
148 pub fn dtype(&self) -> DType {
149 self.shape().dtype()
150 }
151
152 pub fn rank(&self) -> usize {
153 self.shape().rank()
154 }
155
156 pub fn dims(&self) -> Vec<usize> {
158 self.shape()
159 .dims()
160 .iter()
161 .map(|d| match d {
162 Dim::Static(n) => *n,
163 Dim::Dynamic(_) => panic!("dims(): tensor has a dynamic axis"),
164 })
165 .collect()
166 }
167
168 pub fn numel(&self) -> usize {
170 self.dims().iter().product()
171 }
172
173 pub fn is_static_shape(&self) -> bool {
175 self.shape().is_static()
176 }
177
178 pub fn shares_graph(&self, other: &Tensor) -> bool {
183 self.handle.same(&other.handle)
184 }
185
186 pub fn storage_bytes(&self) -> usize {
191 self.handle.with_graph(|g| {
192 g.nodes()
193 .iter()
194 .filter_map(|n| match &n.op {
195 Op::Constant { data } => Some(data.len()),
196 _ => None,
197 })
198 .sum()
199 })
200 }
201
202 pub fn slice(&self, spec: SliceSpec) -> Self {
204 spec.apply(self)
205 }
206
207 fn map_unary(&self, f: impl FnOnce(&mut rlx_ir::Graph, NodeId) -> NodeId) -> Self {
208 let id = self.handle.with_graph(|g| f(g, self.id));
209 Self::new(self.handle.clone(), id)
210 }
211
212 pub(crate) fn adopt(&self, other: &Tensor) -> NodeId {
215 self.handle.adopt(&other.handle, other.id)
216 }
217
218 fn map_binary(
219 &self,
220 rhs: BinaryRhs<'_>,
221 f: impl FnOnce(&mut rlx_ir::Graph, NodeId, NodeId) -> NodeId,
222 ) -> Self {
223 let rhs_id = match rhs {
224 BinaryRhs::Tensor(v) => self.adopt(v),
225 BinaryRhs::Scalar(s) => self.handle.with_graph(|g| {
226 let dtype = g.shape(self.id).dtype();
227 promote_scalar(g, s, dtype)
228 }),
229 };
230 let id = self.handle.with_graph(|g| f(g, self.id, rhs_id));
231 Self::new(self.handle.clone(), id)
232 }
233
234 fn map_act(&self, act: Activation) -> Self {
236 self.map_unary(|g, x| {
237 let s = rlx_ir::shape::unary_shape(g.shape(x));
238 g.activation(act, x, s)
239 })
240 }
241
242 fn map_binary_op(&self, rhs: BinaryRhs<'_>, op: BinaryOp) -> Self {
244 self.map_binary(rhs, |g, a, b| {
245 let s = rlx_ir::shape::binary_shape(g.shape(a), g.shape(b))
246 .expect("binary shape inference");
247 g.binary(op, a, b, s)
248 })
249 }
250
251 fn map_cmp(&self, rhs: BinaryRhs<'_>, op: CmpOp) -> Self {
253 self.map_binary(rhs, |g, a, b| {
254 let s = rlx_ir::shape::compare_shape(g.shape(a), g.shape(b))
255 .expect("compare shape inference");
256 g.add_node(Op::Compare(op), vec![a, b], s)
257 })
258 }
259
260 fn map_reduce(&self, op: ReduceOp, axes: Vec<usize>, keep_dim: bool) -> Self {
262 self.map_unary(|g, x| {
263 let s = rlx_ir::shape::reduce_shape(g.shape(x), &axes, keep_dim)
264 .expect("reduce shape inference");
265 g.reduce(x, op, axes, keep_dim, s)
266 })
267 }
268
269 #[doc(alias = "@")]
271 pub fn mm(&self, rhs: &Tensor) -> Self {
272 self.matmul(rhs)
273 }
274
275 pub fn matmul(&self, rhs: &Tensor) -> Self {
276 self.map_binary(BinaryRhs::Tensor(rhs), |g, a, b| g.mm(a, b))
277 }
278
279 pub fn scaled_matmul(&self, rhs: &Tensor, fmt: ScaledFormat, layout: ScaleLayout) -> Self {
286 self.map_binary(BinaryRhs::Tensor(rhs), move |g, a, b| {
287 g.scaled_matmul(a, b, fmt, layout)
288 })
289 }
290
291 pub fn softmax(&self, axis: i32) -> Self {
292 self.map_unary(|g, x| g.sm(x, axis))
293 }
294
295 pub fn reshape(&self, new_shape: impl Into<Vec<i64>>) -> Self {
296 self.map_unary(|g, x| g.reshape_(x, new_shape.into()))
297 }
298
299 pub fn transpose(&self, perm: impl Into<Vec<usize>>) -> Self {
300 self.map_unary(|g, x| g.transpose_(x, perm.into()))
301 }
302
303 pub fn t(&self) -> Self {
304 let rank = self.rank();
305 assert!(rank >= 2, "t() requires rank >= 2");
306 let perm: Vec<usize> = (0..rank - 2).chain([rank - 1, rank - 2]).collect();
307 self.transpose(perm)
308 }
309
310 pub fn cast(&self, to: DType) -> Self {
311 self.map_unary(|g, x| g.cast(x, to))
312 }
313
314 pub fn narrow(&self, axis: usize, start: usize, len: usize) -> Self {
315 self.map_unary(|g, x| g.narrow_(x, axis, start, len))
316 }
317
318 pub fn broadcast_to(&self, shape: impl Into<Vec<i64>>) -> Self {
322 let target = shape.into();
323 self.map_unary(|g, x| {
324 let s = rlx_ir::shape::expand_shape(g.shape(x), &target)
325 .expect("broadcast_to shape inference");
326 g.add_node(
327 Op::Expand {
328 target_shape: target,
329 },
330 vec![x],
331 s,
332 )
333 })
334 }
335
336 pub fn flatten(&self) -> Self {
338 self.reshape(vec![-1_i64])
339 }
340
341 pub fn unsqueeze(&self, axis: usize) -> Self {
344 let mut dims = self.dims_i64();
345 assert!(axis <= dims.len(), "unsqueeze: axis {axis} out of range");
346 dims.insert(axis, 1);
347 self.reshape(dims)
348 }
349
350 pub fn squeeze(&self, axis: usize) -> Self {
352 let dims = self.shape();
353 let dims = dims.dims();
354 assert!(
355 matches!(dims.get(axis), Some(Dim::Static(1))),
356 "squeeze: axis {axis} is not size 1"
357 );
358 let new: Vec<i64> = dims
359 .iter()
360 .enumerate()
361 .filter(|(i, _)| *i != axis)
362 .map(|(_, d)| dim_to_i64(d))
363 .collect();
364 self.reshape(new)
365 }
366
367 pub fn squeeze_all(&self) -> Self {
369 let new: Vec<i64> = self
370 .shape()
371 .dims()
372 .iter()
373 .filter(|d| !matches!(d, Dim::Static(1)))
374 .map(dim_to_i64)
375 .collect();
376 self.reshape(new)
377 }
378
379 fn dims_i64(&self) -> Vec<i64> {
381 self.shape().dims().iter().map(dim_to_i64).collect()
382 }
383
384 pub fn split(&self, axis: usize, sizes: &[usize]) -> Vec<Tensor> {
387 let mut out = Vec::with_capacity(sizes.len());
388 let mut start = 0;
389 for &len in sizes {
390 out.push(self.narrow(axis, start, len));
391 start += len;
392 }
393 out
394 }
395
396 pub fn chunk(&self, axis: usize, n: usize) -> Vec<Tensor> {
398 assert!(n >= 1, "chunk: n must be >= 1");
399 let total = self.dims()[axis];
400 let base = total / n;
401 let rem = total % n;
402 let sizes: Vec<usize> = (0..n).map(|i| base + usize::from(i < rem)).collect();
403 self.split(axis, &sizes)
404 }
405
406 pub fn tile(&self, axis: usize, reps: usize) -> Tensor {
409 assert!(reps >= 1, "tile: reps must be >= 1");
410 let copies = vec![self; reps];
411 crate::array::cat(&copies, axis)
412 }
413
414 pub fn flip(&self, axis: usize) -> Tensor {
416 let n = self.dims()[axis];
417 let rev: Vec<i64> = (0..n as i64).rev().collect();
418 self.index_select(axis, &Tensor::index_vec(rev))
419 }
420
421 pub fn roll(&self, axis: usize, shift: usize) -> Tensor {
423 let n = self.dims()[axis];
424 let s = if n == 0 { 0 } else { shift % n };
425 if s == 0 {
426 return self.clone();
427 }
428 let tail = self.narrow(axis, n - s, s);
429 let head = self.narrow(axis, 0, n - s);
430 crate::array::cat(&[&tail, &head], axis)
431 }
432
433 pub fn pad(&self, axis: usize, before: usize, after: usize, value: f32) -> Tensor {
435 let mut parts: Vec<Tensor> = Vec::new();
436 if before > 0 {
437 let mut d = self.dims();
438 d[axis] = before;
439 parts.push(Tensor::full(&d, value));
440 }
441 parts.push(self.clone());
442 if after > 0 {
443 let mut d = self.dims();
444 d[axis] = after;
445 parts.push(Tensor::full(&d, value));
446 }
447 let refs: Vec<&Tensor> = parts.iter().collect();
448 crate::array::cat(&refs, axis)
449 }
450
451 pub fn gather(&self, indices: &Tensor, axis: usize) -> Self {
452 self.map_binary(BinaryRhs::Tensor(indices), |g, table, idx| {
453 g.gather_(table, idx, axis)
454 })
455 }
456
457 pub fn index_select(&self, axis: usize, indices: &Tensor) -> Self {
460 self.gather(indices, axis)
461 }
462
463 pub fn select(&self, axis: usize, index: i64) -> Self {
466 let dim = match self.shape().dims().get(axis) {
467 Some(Dim::Static(n)) => Some(*n),
468 _ => None,
469 };
470 let pos = crate::slice::resolve_index(index, dim, axis);
471 self.narrow(axis, pos, 1).squeeze(axis)
472 }
473
474 pub fn stop_gradient(&self) -> Self {
475 self.map_unary(|g, x| g.stop_gradient(x))
476 }
477
478 pub fn argmax(&self, axis: usize, keep_dim: bool) -> Self {
481 self.map_unary(|g, x| {
482 let s = rlx_ir::shape::reduce_shape(g.shape(x), &[axis], keep_dim)
483 .expect("argmax shape inference");
484 g.argmax(x, axis, keep_dim, s)
485 })
486 }
487
488 pub fn argmin(&self, axis: usize, keep_dim: bool) -> Self {
490 self.map_unary(|g, x| {
491 let s = rlx_ir::shape::reduce_shape(g.shape(x), &[axis], keep_dim)
492 .expect("argmin shape inference");
493 g.argmin(x, axis, keep_dim, s)
494 })
495 }
496
497 pub fn inv(&self) -> Self {
501 let dims = self.dims();
502 assert!(
503 dims.len() == 2 && dims[0] == dims[1],
504 "inv() requires a square 2-D matrix, got {dims:?}"
505 );
506 let n = dims[0];
507 let eye_id = self.adopt(&Tensor::eye(n));
508 let id = self.handle.with_graph(|g| {
509 let s = Shape::new(&[n, n], DType::F32);
510 g.dense_solve(self.id, eye_id, s)
511 });
512 Self::new(self.handle.clone(), id)
513 }
514
515 pub fn solve(&self, b: &Tensor) -> Self {
518 let s = b.shape();
519 let b_id = self.adopt(b);
520 let id = self.handle.with_graph(|g| g.dense_solve(self.id, b_id, s));
521 Self::new(self.handle.clone(), id)
522 }
523
524 pub fn cumsum(&self, axis: i32, exclusive: bool) -> Self {
526 self.map_unary(|g, x| {
527 let s = rlx_ir::shape::unary_shape(g.shape(x));
528 g.cumsum(x, axis, exclusive, s)
529 })
530 }
531
532 pub fn var(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
534 let axes = axes.into();
535 let mu = self.mean(axes.clone(), true);
536 let centered = self - μ
537 (¢ered * ¢ered).mean(axes, keep_dim)
538 }
539
540 pub fn std(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
542 self.var(axes, keep_dim).sqrt()
543 }
544
545 pub fn norm(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
547 (self * self).sum(axes, keep_dim).sqrt()
548 }
549
550 pub fn logsumexp(&self, axis: usize, keep_dim: bool) -> Self {
553 let m = self.max([axis], true);
554 let shifted = self - &m;
555 let summed = shifted.exp().sum([axis], true);
556 let lse = &summed.log() + &m;
557 if keep_dim { lse } else { lse.squeeze(axis) }
558 }
559
560 pub fn fft(&self) -> Self {
564 self.map_unary(|g, x| g.fft(x, false))
565 }
566
567 pub fn ifft(&self) -> Self {
570 self.map_unary(|g, x| g.fft(x, true))
571 }
572
573 pub fn fft_axis(&self, axis: usize, inverse: bool) -> Self {
575 self.map_unary(|g, x| g.fft_axis(x, axis, inverse))
576 }
577
578 pub fn layer_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.handle.with_graph(|g| {
582 let s = rlx_ir::shape::unary_shape(g.shape(self.id));
583 g.layer_norm(self.id, g_id, b_id, -1, eps, s)
584 });
585 Self::new(self.handle.clone(), id)
586 }
587
588 pub fn rms_norm(&self, gamma: &Tensor, beta: &Tensor, eps: f32) -> Self {
589 let g_id = self.adopt(gamma);
590 let b_id = self.adopt(beta);
591 let id = self
592 .handle
593 .with_graph(|g| g.rms_norm(self.id, g_id, b_id, eps));
594 Self::new(self.handle.clone(), id)
595 }
596
597 pub fn cross_entropy(&self, targets: &Tensor) -> Self {
607 let t_id = self.adopt(targets);
608 let id = self
609 .handle
610 .with_graph(|g| g.softmax_cross_entropy(self.id, t_id));
611 Self::new(self.handle.clone(), id)
612 }
613
614 pub fn softmax_cross_entropy_with_logits(&self, labels: &Tensor) -> Self {
619 let l_id = self.adopt(labels);
620 let id = self
621 .handle
622 .with_graph(|g| g.softmax_cross_entropy_with_logits(self.id, l_id));
623 Self::new(self.handle.clone(), id)
624 }
625
626 pub fn mean_all(&self) -> Self {
630 let r = self.rank();
631 if r == 0 {
632 return self.clone();
633 }
634 self.mean((0..r).collect::<Vec<usize>>(), false)
635 }
636
637 pub fn sum_all(&self) -> Self {
640 let r = self.rank();
641 if r == 0 {
642 return self.clone();
643 }
644 self.sum((0..r).collect::<Vec<usize>>(), false)
645 }
646
647 pub fn conv2d(
648 &self,
649 weight: &Tensor,
650 kernel_size: [usize; 2],
651 stride: [usize; 2],
652 padding: [usize; 2],
653 dilation: [usize; 2],
654 groups: usize,
655 ) -> Self {
656 let w_id = self.adopt(weight);
657 let id = self.handle.with_graph(|g| {
658 g.conv2d(
659 self.id,
660 w_id,
661 kernel_size,
662 stride,
663 padding,
664 dilation,
665 groups,
666 )
667 });
668 Self::new(self.handle.clone(), id)
669 }
670
671 pub fn attention(
672 &self,
673 k: &Tensor,
674 v: &Tensor,
675 num_heads: usize,
676 head_dim: usize,
677 mask_kind: MaskKind,
678 ) -> Self {
679 let k_id = self.adopt(k);
680 let v_id = self.adopt(v);
681 let id = self.handle.with_graph(|g| {
682 let s = rlx_ir::shape::attention_shape(g.shape(self.id));
683 g.attention_kind(self.id, k_id, v_id, num_heads, head_dim, mask_kind, s)
684 });
685 Self::new(self.handle.clone(), id)
686 }
687
688 pub fn synth_matmul(
695 &self,
696 indices: &Tensor,
697 codebook: &Tensor,
698 entry_dim: u32,
699 num_entries: u32,
700 ) -> Self {
701 let idx_id = self.adopt(indices);
702 let cb_id = self.adopt(codebook);
703 let id = self.handle.with_graph(|g| {
704 let m = g.shape(self.id).dim(0).unwrap_static();
705 let n = g.shape(idx_id).dim(0).unwrap_static();
706 let out = rlx_ir::Shape::new(&[m, n], rlx_ir::DType::F32);
707 g.synth_matmul(
708 self.id,
709 idx_id,
710 cb_id,
711 rlx_ir::SynthKind::Codebook {
712 entry_dim,
713 num_entries,
714 },
715 out,
716 )
717 });
718 Self::new(self.handle.clone(), id)
719 }
720
721 pub fn synth_reconstruct(&self, codebook: &Tensor, entry_dim: u32) -> Self {
729 let cb_id = self.adopt(codebook);
730 let id = self.handle.with_graph(|g| {
731 use rlx_ir::{DType, Op, Shape};
732 let d = entry_dim as usize;
743 let idx_shape = g.shape(self.id);
744 let n = idx_shape.dim(0).unwrap_static();
745 let kb = idx_shape.dim(1).unwrap_static();
746 let (k, p) = (kb * d, n * kb);
747 let idx_i64 = g.add_node(
748 Op::Cast { to: DType::I64 },
749 vec![self.id],
750 Shape::new(&[n, kb], DType::I64),
751 );
752 let idx_flat = g.add_node(
753 Op::Reshape {
754 new_shape: vec![p as i64],
755 },
756 vec![idx_i64],
757 Shape::new(&[p], DType::I64),
758 );
759 let rows = g.add_node(
760 Op::Gather { axis: 0 },
761 vec![cb_id, idx_flat],
762 Shape::new(&[p, d], DType::F32),
763 );
764 let w_bt = g.add_node(
765 Op::Reshape {
766 new_shape: vec![n as i64, k as i64],
767 },
768 vec![rows],
769 Shape::new(&[n, k], DType::F32),
770 );
771 g.add_node(
772 Op::Transpose { perm: vec![1, 0] },
773 vec![w_bt],
774 Shape::new(&[k, n], DType::F32),
775 )
776 });
777 Self::new(self.handle.clone(), id)
778 }
779
780 pub fn spline_activation(
786 &self,
787 coeff: &Tensor,
788 num_basis: u32,
789 grid_min: f32,
790 grid_max: f32,
791 ) -> Self {
792 let c_id = self.adopt(coeff);
793 let id = self
794 .handle
795 .with_graph(|g| g.spline_activation(self.id, c_id, num_basis, grid_min, grid_max));
796 Self::new(self.handle.clone(), id)
797 }
798
799 pub fn linear(&self, w: &Tensor, b: &Tensor) -> Self {
805 self.linear_act(w, b, None)
806 }
807
808 pub fn linear_act(&self, w: &Tensor, b: &Tensor, act: Option<Activation>) -> Self {
811 let w_id = self.adopt(w);
812 let b_id = self.adopt(b);
813 let id = self.handle.with_graph(|g| {
814 let rank = g.shape(w_id).rank();
815 let wt = g.transpose_(w_id, vec![rank - 1, rank - 2]);
816 let s = rlx_ir::shape::matmul_shape(g.shape(self.id), g.shape(wt))
817 .expect("linear shape inference");
818 g.fused_matmul_bias_act(self.id, wt, b_id, act, s)
819 });
820 Self::new(self.handle.clone(), id)
821 }
822
823 pub fn matmul_t(&self, rhs: &Tensor) -> Self {
827 self.matmul(&rhs.t())
828 }
829
830 pub fn attention_masked(
836 &self,
837 k: &Tensor,
838 v: &Tensor,
839 mask: &Tensor,
840 num_heads: usize,
841 head_dim: usize,
842 ) -> Self {
843 let k_id = self.adopt(k);
844 let v_id = self.adopt(v);
845 let m_id = self.adopt(mask);
846 let id = self.handle.with_graph(|g| {
847 let s = rlx_ir::shape::attention_shape(g.shape(self.id));
848 g.attention(self.id, k_id, v_id, m_id, num_heads, head_dim, s)
849 });
850 Self::new(self.handle.clone(), id)
851 }
852
853 pub fn rope(&self, cos: &Tensor, sin: &Tensor, head_dim: usize) -> Self {
854 let cos_id = self.adopt(cos);
855 let sin_id = self.adopt(sin);
856 let id = self
857 .handle
858 .with_graph(|g| g.rope(self.id, cos_id, sin_id, head_dim));
859 Self::new(self.handle.clone(), id)
860 }
861
862 pub fn where_<'t, 'f>(
867 &self,
868 on_true: impl Into<BinaryRhs<'t>>,
869 on_false: impl Into<BinaryRhs<'f>>,
870 ) -> Self {
871 let t = on_true.into();
872 let f = on_false.into();
873 let hint = if let BinaryRhs::Tensor(x) = &t {
874 x.dtype()
875 } else if let BinaryRhs::Tensor(x) = &f {
876 x.dtype()
877 } else {
878 DType::F32
879 };
880 let t_id = self.where_branch(t, hint);
881 let f_id = self.where_branch(f, hint);
882 let id = self.handle.with_graph(|g| {
883 let s = rlx_ir::shape::binary_shape(g.shape(t_id), g.shape(f_id))
884 .expect("where shape inference");
885 g.add_node(Op::Where, vec![self.id, t_id, f_id], s)
886 });
887 Self::new(self.handle.clone(), id)
888 }
889
890 fn where_branch(&self, rhs: BinaryRhs<'_>, hint: DType) -> NodeId {
893 match rhs {
894 BinaryRhs::Tensor(t) => self.adopt(t),
895 BinaryRhs::Scalar(s) => self.handle.with_graph(|g| promote_scalar(g, s, hint)),
896 }
897 }
898
899 pub fn masked_fill(&self, mask: &Tensor, value: f64) -> Self {
903 let dims: Vec<usize> = self
904 .shape()
905 .dims()
906 .iter()
907 .map(|d| match d {
908 Dim::Static(n) => *n,
909 Dim::Dynamic(_) => panic!("masked_fill requires a static shape"),
910 })
911 .collect();
912 let fill = Tensor::full(dims, value as f32);
913 mask.where_(&fill, self)
914 }
915
916 pub fn maximum<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
918 self.map_binary_op(rhs.into(), BinaryOp::Max)
919 }
920
921 pub fn minimum<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
923 self.map_binary_op(rhs.into(), BinaryOp::Min)
924 }
925
926 pub fn atan2<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
928 self.map_binary_op(rhs.into(), BinaryOp::Atan2)
929 }
930
931 pub fn rem<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
933 self.map_binary_op(rhs.into(), BinaryOp::Mod)
934 }
935
936 pub fn clamp(&self, min: f64, max: f64) -> Self {
938 self.clamp_min(min).clamp_max(max)
939 }
940
941 pub fn clamp_min(&self, min: f64) -> Self {
943 self.map_binary_op(BinaryRhs::Scalar(min.into()), BinaryOp::Max)
944 }
945
946 pub fn clamp_max(&self, max: f64) -> Self {
948 self.map_binary_op(BinaryRhs::Scalar(max.into()), BinaryOp::Min)
949 }
950
951 pub fn pow<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
953 self.map_binary_op(rhs.into(), BinaryOp::Pow)
954 }
955
956 pub fn pow_scalar(&self, exp: f64) -> Self {
957 self.map_binary_op(BinaryRhs::Scalar(exp.into()), BinaryOp::Pow)
958 }
959}
960
961unary_ops! {
962 relu => Relu,
964 gelu => Gelu,
966 gelu_approx => GeluApprox,
968 silu => Silu,
970 tanh => Tanh,
972 exp => Exp,
974 sqrt => Sqrt,
976 sigmoid => Sigmoid,
978 log => Log,
980 rsqrt => Rsqrt,
982 abs => Abs,
984 sin => Sin,
986 cos => Cos,
988 tan => Tan,
990 atan => Atan,
992 round => Round,
994 recip => Recip,
996 floor => Floor,
998 ceil => Ceil,
1000 sign => Sign,
1002 softplus => Softplus,
1004 elu => Elu,
1006 erf => Erf,
1008 hard_swish => HardSwish,
1010 hard_sigmoid => HardSigmoid,
1012 mish => Mish,
1014 softsign => Softsign,
1016 log_sigmoid => LogSigmoid,
1018}
1019
1020cmp_ops! {
1021 eq => Eq,
1023 ne => Ne,
1025 lt => Lt,
1027 le => Le,
1029 gt => Gt,
1031 ge => Ge,
1033}
1034
1035reduce_ops! {
1036 sum => Sum,
1038 mean => Mean,
1040 max => Max,
1042 min => Min,
1044 prod => Prod,
1046}
1047
1048#[cfg(feature = "eval")]
1052macro_rules! pod_readers {
1053 ($($(#[$m:meta])* $name:ident => ($ty:ty, $dt:ident, $width:literal)),+ $(,)?) => {
1054 $(
1055 $(#[$m])*
1056 pub fn $name(&self) -> Vec<$ty> {
1057 let (bytes, dt) = self.eval_typed();
1058 assert_eq!(
1059 dt, DType::$dt,
1060 concat!(stringify!($name), ": tensor dtype is {:?}"), dt,
1061 );
1062 bytes
1063 .chunks_exact($width)
1064 .map(|c| <$ty>::from_le_bytes(c.try_into().unwrap()))
1065 .collect()
1066 }
1067 )+
1068 };
1069}
1070
1071#[cfg(feature = "eval")]
1076impl Tensor {
1077 pub fn to_vec(&self) -> Vec<f32> {
1081 self.to_vec_on(self.auto_device())
1082 }
1083
1084 pub fn to_vec_on(&self, device: rlx_runtime::Device) -> Vec<f32> {
1086 if let Some((bytes, DType::F32)) = self.constant_payload() {
1091 return bytes
1092 .chunks_exact(4)
1093 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
1094 .collect();
1095 }
1096 let compiled = self.compiled_on(device);
1097 let mut outputs = compiled.borrow_mut().run(&[]);
1098 outputs.pop().expect("eval produced no output")
1099 }
1100
1101 fn constant_payload(&self) -> Option<(Vec<u8>, DType)> {
1106 self.handle.with_graph(|g| {
1107 let node = g.node(self.id);
1108 if let Op::Constant { data } = &node.op {
1109 if node.shape.size_bytes() == Some(data.len()) {
1110 return Some((data.clone(), node.shape.dtype()));
1111 }
1112 }
1113 None
1114 })
1115 }
1116
1117 fn auto_device(&self) -> rlx_runtime::Device {
1119 self.handle
1120 .with_graph(|g| rlx_runtime::fastest_device_for(g))
1121 }
1122
1123 fn compiled_on(
1126 &self,
1127 device: rlx_runtime::Device,
1128 ) -> std::rc::Rc<std::cell::RefCell<rlx_runtime::CompiledGraph>> {
1129 self.handle
1130 .with_graph(|g| crate::cache::compiled_output(g, self.id, device))
1131 }
1132
1133 pub fn realize(&self) -> Vec<f32> {
1139 self.to_vec()
1140 }
1141
1142 pub fn realize_on(&self, device: rlx_runtime::Device) -> Vec<f32> {
1144 self.to_vec_on(device)
1145 }
1146
1147 pub fn item(&self) -> f32 {
1149 let v = self.to_vec();
1150 assert_eq!(v.len(), 1, "item(): tensor has {} elements, not 1", v.len());
1151 v[0]
1152 }
1153
1154 pod_readers! {
1155 to_vec_f64 => (f64, F64, 8),
1157 to_vec_i64 => (i64, I64, 8),
1159 to_vec_i32 => (i32, I32, 4),
1161 }
1162
1163 pub fn to_vec_bool(&self) -> Vec<bool> {
1167 let (bytes, dt) = self.eval_typed();
1168 assert_eq!(dt, DType::Bool, "to_vec_bool: tensor dtype is {dt:?}");
1169 let n = self.numel();
1170 assert!(n > 0 && bytes.len() % n == 0, "to_vec_bool: ragged output");
1171 let w = bytes.len() / n;
1172 bytes
1173 .chunks_exact(w)
1174 .map(|c| c.iter().any(|&b| b != 0))
1175 .collect()
1176 }
1177
1178 fn eval_typed(&self) -> (Vec<u8>, DType) {
1181 if let Some(ct) = self.constant_payload() {
1183 return ct;
1184 }
1185 let compiled = self.compiled_on(self.auto_device());
1186 let mut outputs = compiled.borrow_mut().run_typed(&[]);
1187 outputs.pop().expect("eval produced no output")
1188 }
1189
1190 pub fn show(&self) -> String {
1193 let dims = self.dims();
1194 let data = self.to_vec();
1195 format!(
1196 "Tensor{dims:?} {:?}\n{}",
1197 self.dtype(),
1198 fmt_nested(&data, &dims)
1199 )
1200 }
1201
1202 pub fn on(&self, device: rlx_runtime::Device) -> Materialize<'_> {
1204 Materialize {
1205 tensor: self,
1206 device,
1207 }
1208 }
1209}
1210
1211#[cfg(feature = "eval")]
1213pub struct Materialize<'a> {
1214 tensor: &'a Tensor,
1215 device: rlx_runtime::Device,
1216}
1217
1218#[cfg(feature = "eval")]
1219impl Materialize<'_> {
1220 pub fn to_vec(self) -> Vec<f32> {
1222 self.tensor.to_vec_on(self.device)
1223 }
1224}
1225
1226#[cfg(feature = "autodiff")]
1229impl Tensor {
1230 pub fn grad(&self, wrt: &[&Tensor]) -> Vec<Tensor> {
1244 let wrt_ids: Vec<NodeId> = wrt.iter().map(|t| self.adopt(t)).collect();
1248 let loss_id = self.id;
1249 let bwd = self.handle.with_graph(|g| {
1250 let mut forward = g.clone();
1251 forward.set_outputs(vec![loss_id]);
1252 let mut bwd = rlx_autodiff::grad(&forward, &wrt_ids);
1253 bake_unit_seed(&mut bwd);
1254 bwd
1255 });
1256 let handle = GraphHandle::new(bwd);
1257 let outputs = handle.with_graph(|g| g.outputs.clone());
1258 outputs
1259 .into_iter()
1260 .map(|id| Tensor::new(handle.clone(), id))
1261 .collect()
1262 }
1263}
1264
1265#[cfg(feature = "autodiff")]
1268pub(crate) fn bake_unit_seed(bwd: &mut rlx_ir::Graph) {
1269 let seed = bwd.nodes().iter().find_map(|n| match &n.op {
1270 Op::Input { name } if name == "d_output" => Some(n.id),
1271 _ => None,
1272 });
1273 if let Some(id) = seed {
1274 let n = bwd.node(id).shape.num_elements().unwrap_or(1);
1275 let data: Vec<u8> = (0..n).flat_map(|_| 1.0f32.to_le_bytes()).collect();
1276 let node = bwd.node_mut(id);
1277 node.op = Op::Constant { data };
1278 node.inputs = Vec::new();
1279 }
1280}
1281
1282impl From<Tensor> for NodeId {
1283 fn from(v: Tensor) -> Self {
1284 v.id
1285 }
1286}
1287
1288impl From<&Tensor> for NodeId {
1289 fn from(v: &Tensor) -> Self {
1290 v.id
1291 }
1292}
1293
1294pub(crate) enum BinaryRhs<'a> {
1295 Tensor(&'a Tensor),
1296 Scalar(Scalar),
1297}
1298
1299impl<'a> BinaryRhs<'a> {
1300 fn scalar(s: Scalar) -> Self {
1301 Self::Scalar(s)
1302 }
1303}
1304
1305impl<'a> From<&'a Tensor> for BinaryRhs<'a> {
1306 fn from(v: &'a Tensor) -> Self {
1307 Self::Tensor(v)
1308 }
1309}
1310
1311macro_rules! impl_scalar_rhs {
1312 ($($ty:ty),+ $(,)?) => {
1313 $(
1314 impl From<$ty> for BinaryRhs<'_> {
1315 fn from(v: $ty) -> Self {
1316 Self::Scalar(v.into())
1317 }
1318 }
1319 )+
1320 };
1321}
1322
1323impl_scalar_rhs!(bool, i32, i64, f32, f64);
1324
1325macro_rules! impl_tensor_binop {
1326 ($trait:ident, $method:ident, $graph:ident) => {
1327 impl $trait<&Tensor> for &Tensor {
1328 type Output = Tensor;
1329
1330 fn $method(self, rhs: &Tensor) -> Tensor {
1331 self.map_binary(BinaryRhs::Tensor(rhs), |g, a, b| g.$graph(a, b))
1332 }
1333 }
1334
1335 impl $trait<Tensor> for &Tensor {
1336 type Output = Tensor;
1337
1338 fn $method(self, rhs: Tensor) -> Tensor {
1339 self.$method(&rhs)
1340 }
1341 }
1342
1343 impl $trait<f64> for &Tensor {
1344 type Output = Tensor;
1345
1346 fn $method(self, rhs: f64) -> Tensor {
1347 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1348 }
1349 }
1350
1351 impl $trait<f32> for &Tensor {
1352 type Output = Tensor;
1353
1354 fn $method(self, rhs: f32) -> Tensor {
1355 self.$method(rhs as f64)
1356 }
1357 }
1358
1359 impl $trait<i32> for &Tensor {
1360 type Output = Tensor;
1361
1362 fn $method(self, rhs: i32) -> Tensor {
1363 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1364 }
1365 }
1366
1367 impl $trait<&Tensor> for Tensor {
1368 type Output = Tensor;
1369
1370 fn $method(self, rhs: &Tensor) -> Tensor {
1371 (&self).$method(rhs)
1372 }
1373 }
1374
1375 impl $trait<Tensor> for Tensor {
1376 type Output = Tensor;
1377
1378 fn $method(self, rhs: Tensor) -> Tensor {
1379 (&self).$method(&rhs)
1380 }
1381 }
1382
1383 impl $trait<f64> for Tensor {
1384 type Output = Tensor;
1385
1386 fn $method(self, rhs: f64) -> Tensor {
1387 (&self).$method(rhs)
1388 }
1389 }
1390
1391 impl $trait<f32> for Tensor {
1392 type Output = Tensor;
1393
1394 fn $method(self, rhs: f32) -> Tensor {
1395 (&self).$method(rhs)
1396 }
1397 }
1398
1399 impl $trait<i32> for Tensor {
1400 type Output = Tensor;
1401
1402 fn $method(self, rhs: i32) -> Tensor {
1403 (&self).$method(rhs)
1404 }
1405 }
1406 };
1407}
1408
1409impl_tensor_binop!(Add, add, add);
1410impl_tensor_binop!(Sub, sub, sub);
1411impl_tensor_binop!(Mul, mul, mul);
1412impl_tensor_binop!(Div, div, div);
1413
1414impl Neg for &Tensor {
1415 type Output = Tensor;
1416
1417 fn neg(self) -> Tensor {
1418 self.map_unary(|g, x| g.neg(x))
1419 }
1420}
1421
1422macro_rules! impl_scalar_left {
1423 ($trait:ident, $method:ident, $graph:ident) => {
1424 impl $trait<&Tensor> for f64 {
1425 type Output = Tensor;
1426
1427 fn $method(self, rhs: &Tensor) -> Tensor {
1428 let id = rhs.handle.with_graph(|g| {
1429 let lhs_id = promote_scalar(g, self.into(), g.shape(rhs.id).dtype());
1430 g.$graph(lhs_id, rhs.id)
1431 });
1432 Tensor::new(rhs.handle.clone(), id)
1433 }
1434 }
1435
1436 impl $trait<&Tensor> for f32 {
1437 type Output = Tensor;
1438
1439 fn $method(self, rhs: &Tensor) -> Tensor {
1440 (self as f64).$method(rhs)
1441 }
1442 }
1443
1444 impl $trait<&Tensor> for i32 {
1445 type Output = Tensor;
1446
1447 fn $method(self, rhs: &Tensor) -> Tensor {
1448 (self as i64 as f64).$method(rhs)
1449 }
1450 }
1451 };
1452}
1453
1454impl_scalar_left!(Add, add, add);
1455impl_scalar_left!(Sub, sub, sub);
1456impl_scalar_left!(Mul, mul, mul);
1457impl_scalar_left!(Div, div, div);
1458
1459impl Neg for Tensor {
1460 type Output = Tensor;
1461
1462 fn neg(self) -> Tensor {
1463 (&self).neg()
1464 }
1465}