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 fn full_on(&self, dims: &[usize], value: f32) -> Tensor {
441 let n: usize = dims.iter().product();
442 let bytes: Vec<u8> = std::iter::repeat_n(value, n)
443 .flat_map(f32::to_le_bytes)
444 .collect();
445 let shape = Shape::new(dims, DType::F32);
446 let id = self
447 .handle
448 .with_graph(|g| g.add_node(Op::Constant { data: bytes }, vec![], shape));
449 Self::new(self.handle.clone(), id)
450 }
451
452 pub fn pad(&self, axis: usize, before: usize, after: usize, value: f32) -> Tensor {
454 let mut parts: Vec<Tensor> = Vec::new();
455 if before > 0 {
456 let mut d = self.dims();
457 d[axis] = before;
458 parts.push(self.full_on(&d, value));
459 }
460 parts.push(self.clone());
461 if after > 0 {
462 let mut d = self.dims();
463 d[axis] = after;
464 parts.push(self.full_on(&d, value));
465 }
466 let refs: Vec<&Tensor> = parts.iter().collect();
467 crate::array::cat(&refs, axis)
468 }
469
470 pub fn gather(&self, indices: &Tensor, axis: usize) -> Self {
471 self.map_binary(BinaryRhs::Tensor(indices), |g, table, idx| {
472 g.gather_(table, idx, axis)
473 })
474 }
475
476 pub fn index_select(&self, axis: usize, indices: &Tensor) -> Self {
479 self.gather(indices, axis)
480 }
481
482 pub fn select(&self, axis: usize, index: i64) -> Self {
485 let dim = match self.shape().dims().get(axis) {
486 Some(Dim::Static(n)) => Some(*n),
487 _ => None,
488 };
489 let pos = crate::slice::resolve_index(index, dim, axis);
490 self.narrow(axis, pos, 1).squeeze(axis)
491 }
492
493 pub fn stop_gradient(&self) -> Self {
494 self.map_unary(|g, x| g.stop_gradient(x))
495 }
496
497 pub fn argmax(&self, axis: usize, keep_dim: bool) -> Self {
500 self.map_unary(|g, x| {
501 let s = rlx_ir::shape::reduce_shape(g.shape(x), &[axis], keep_dim)
502 .expect("argmax shape inference");
503 g.argmax(x, axis, keep_dim, s)
504 })
505 }
506
507 pub fn argmin(&self, axis: usize, keep_dim: bool) -> Self {
509 self.map_unary(|g, x| {
510 let s = rlx_ir::shape::reduce_shape(g.shape(x), &[axis], keep_dim)
511 .expect("argmin shape inference");
512 g.argmin(x, axis, keep_dim, s)
513 })
514 }
515
516 pub fn inv(&self) -> Self {
520 let dims = self.dims();
521 assert!(
522 dims.len() == 2 && dims[0] == dims[1],
523 "inv() requires a square 2-D matrix, got {dims:?}"
524 );
525 let n = dims[0];
526 let eye_id = self.adopt(&Tensor::eye(n));
527 let id = self.handle.with_graph(|g| {
528 let s = Shape::new(&[n, n], DType::F32);
529 g.dense_solve(self.id, eye_id, s)
530 });
531 Self::new(self.handle.clone(), id)
532 }
533
534 pub fn solve(&self, b: &Tensor) -> Self {
537 let s = b.shape();
538 let b_id = self.adopt(b);
539 let id = self.handle.with_graph(|g| g.dense_solve(self.id, b_id, s));
540 Self::new(self.handle.clone(), id)
541 }
542
543 pub fn cumsum(&self, axis: i32, exclusive: bool) -> Self {
545 self.map_unary(|g, x| {
546 let s = rlx_ir::shape::unary_shape(g.shape(x));
547 g.cumsum(x, axis, exclusive, s)
548 })
549 }
550
551 pub fn var(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
553 let axes = axes.into();
554 let mu = self.mean(axes.clone(), true);
555 let centered = self - μ
556 (¢ered * ¢ered).mean(axes, keep_dim)
557 }
558
559 pub fn std(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
561 self.var(axes, keep_dim).sqrt()
562 }
563
564 pub fn norm(&self, axes: impl Into<Vec<usize>>, keep_dim: bool) -> Self {
566 (self * self).sum(axes, keep_dim).sqrt()
567 }
568
569 pub fn logsumexp(&self, axis: usize, keep_dim: bool) -> Self {
572 let m = self.max([axis], true);
573 let shifted = self - &m;
574 let summed = shifted.exp().sum([axis], true);
575 let lse = &summed.log() + &m;
576 if keep_dim { lse } else { lse.squeeze(axis) }
577 }
578
579 pub fn fft(&self) -> Self {
583 self.map_unary(|g, x| g.fft(x, false))
584 }
585
586 pub fn ifft(&self) -> Self {
589 self.map_unary(|g, x| g.fft(x, true))
590 }
591
592 pub fn fft_axis(&self, axis: usize, inverse: bool) -> Self {
594 self.map_unary(|g, x| g.fft_axis(x, axis, inverse))
595 }
596
597 pub fn layer_norm(&self, gamma: &Tensor, beta: &Tensor, eps: f32) -> Self {
598 let g_id = self.adopt(gamma);
599 let b_id = self.adopt(beta);
600 let id = self.handle.with_graph(|g| {
601 let s = rlx_ir::shape::unary_shape(g.shape(self.id));
602 g.layer_norm(self.id, g_id, b_id, -1, eps, s)
603 });
604 Self::new(self.handle.clone(), id)
605 }
606
607 pub fn rms_norm(&self, gamma: &Tensor, beta: &Tensor, eps: f32) -> Self {
608 let g_id = self.adopt(gamma);
609 let b_id = self.adopt(beta);
610 let id = self
611 .handle
612 .with_graph(|g| g.rms_norm(self.id, g_id, b_id, eps));
613 Self::new(self.handle.clone(), id)
614 }
615
616 pub fn cross_entropy(&self, targets: &Tensor) -> Self {
626 let t_id = self.adopt(targets);
627 let id = self
628 .handle
629 .with_graph(|g| g.softmax_cross_entropy(self.id, t_id));
630 Self::new(self.handle.clone(), id)
631 }
632
633 pub fn softmax_cross_entropy_with_logits(&self, labels: &Tensor) -> Self {
638 let l_id = self.adopt(labels);
639 let id = self
640 .handle
641 .with_graph(|g| g.softmax_cross_entropy_with_logits(self.id, l_id));
642 Self::new(self.handle.clone(), id)
643 }
644
645 pub fn mean_all(&self) -> Self {
649 let r = self.rank();
650 if r == 0 {
651 return self.clone();
652 }
653 self.mean((0..r).collect::<Vec<usize>>(), false)
654 }
655
656 pub fn sum_all(&self) -> Self {
659 let r = self.rank();
660 if r == 0 {
661 return self.clone();
662 }
663 self.sum((0..r).collect::<Vec<usize>>(), false)
664 }
665
666 pub fn pool2d(
672 &self,
673 kind: ReduceOp,
674 kernel_size: [usize; 2],
675 stride: [usize; 2],
676 padding: [usize; 2],
677 ) -> Self {
678 let id = self
679 .handle
680 .with_graph(|g| g.pool2d(self.id, kind, kernel_size, stride, padding));
681 Self::new(self.handle.clone(), id)
682 }
683
684 pub fn max_pool2d(
694 &self,
695 kernel_size: [usize; 2],
696 stride: [usize; 2],
697 padding: [usize; 2],
698 ) -> Self {
699 self.pool2d(ReduceOp::Max, kernel_size, stride, padding)
700 }
701
702 pub fn avg_pool2d(
704 &self,
705 kernel_size: [usize; 2],
706 stride: [usize; 2],
707 padding: [usize; 2],
708 ) -> Self {
709 self.pool2d(ReduceOp::Mean, kernel_size, stride, padding)
710 }
711
712 pub fn conv2d(
713 &self,
714 weight: &Tensor,
715 kernel_size: [usize; 2],
716 stride: [usize; 2],
717 padding: [usize; 2],
718 dilation: [usize; 2],
719 groups: usize,
720 ) -> Self {
721 let w_id = self.adopt(weight);
722 let id = self.handle.with_graph(|g| {
723 g.conv2d(
724 self.id,
725 w_id,
726 kernel_size,
727 stride,
728 padding,
729 dilation,
730 groups,
731 )
732 });
733 Self::new(self.handle.clone(), id)
734 }
735
736 pub fn attention(
737 &self,
738 k: &Tensor,
739 v: &Tensor,
740 num_heads: usize,
741 head_dim: usize,
742 mask_kind: MaskKind,
743 ) -> Self {
744 let k_id = self.adopt(k);
745 let v_id = self.adopt(v);
746 let id = self.handle.with_graph(|g| {
747 let s = rlx_ir::shape::attention_shape(g.shape(self.id));
748 g.attention_kind(self.id, k_id, v_id, num_heads, head_dim, mask_kind, s)
749 });
750 Self::new(self.handle.clone(), id)
751 }
752
753 pub fn synth_matmul(
760 &self,
761 indices: &Tensor,
762 codebook: &Tensor,
763 entry_dim: u32,
764 num_entries: u32,
765 ) -> Self {
766 let idx_id = self.adopt(indices);
767 let cb_id = self.adopt(codebook);
768 let id = self.handle.with_graph(|g| {
769 let m = g.shape(self.id).dim(0).unwrap_static();
770 let n = g.shape(idx_id).dim(0).unwrap_static();
771 let out = rlx_ir::Shape::new(&[m, n], rlx_ir::DType::F32);
772 g.synth_matmul(
773 self.id,
774 idx_id,
775 cb_id,
776 rlx_ir::SynthKind::Codebook {
777 entry_dim,
778 num_entries,
779 },
780 out,
781 )
782 });
783 Self::new(self.handle.clone(), id)
784 }
785
786 pub fn synth_reconstruct(&self, codebook: &Tensor, entry_dim: u32) -> Self {
794 let cb_id = self.adopt(codebook);
795 let id = self.handle.with_graph(|g| {
796 use rlx_ir::{DType, Op, Shape};
797 let d = entry_dim as usize;
808 let idx_shape = g.shape(self.id);
809 let n = idx_shape.dim(0).unwrap_static();
810 let kb = idx_shape.dim(1).unwrap_static();
811 let (k, p) = (kb * d, n * kb);
812 let idx_i64 = g.add_node(
813 Op::Cast { to: DType::I64 },
814 vec![self.id],
815 Shape::new(&[n, kb], DType::I64),
816 );
817 let idx_flat = g.add_node(
818 Op::Reshape {
819 new_shape: vec![p as i64],
820 },
821 vec![idx_i64],
822 Shape::new(&[p], DType::I64),
823 );
824 let rows = g.add_node(
825 Op::Gather { axis: 0 },
826 vec![cb_id, idx_flat],
827 Shape::new(&[p, d], DType::F32),
828 );
829 let w_bt = g.add_node(
830 Op::Reshape {
831 new_shape: vec![n as i64, k as i64],
832 },
833 vec![rows],
834 Shape::new(&[n, k], DType::F32),
835 );
836 g.add_node(
837 Op::Transpose { perm: vec![1, 0] },
838 vec![w_bt],
839 Shape::new(&[k, n], DType::F32),
840 )
841 });
842 Self::new(self.handle.clone(), id)
843 }
844
845 pub fn spline_activation(
851 &self,
852 coeff: &Tensor,
853 num_basis: u32,
854 grid_min: f32,
855 grid_max: f32,
856 ) -> Self {
857 let c_id = self.adopt(coeff);
858 let id = self
859 .handle
860 .with_graph(|g| g.spline_activation(self.id, c_id, num_basis, grid_min, grid_max));
861 Self::new(self.handle.clone(), id)
862 }
863
864 pub fn linear(&self, w: &Tensor, b: &Tensor) -> Self {
870 self.linear_act(w, b, None)
871 }
872
873 pub fn linear_act(&self, w: &Tensor, b: &Tensor, act: Option<Activation>) -> Self {
876 let w_id = self.adopt(w);
877 let b_id = self.adopt(b);
878 let id = self.handle.with_graph(|g| {
879 let rank = g.shape(w_id).rank();
880 let wt = g.transpose_(w_id, vec![rank - 1, rank - 2]);
881 let s = rlx_ir::shape::matmul_shape(g.shape(self.id), g.shape(wt))
882 .expect("linear shape inference");
883 g.fused_matmul_bias_act(self.id, wt, b_id, act, s)
884 });
885 Self::new(self.handle.clone(), id)
886 }
887
888 pub fn matmul_t(&self, rhs: &Tensor) -> Self {
892 self.matmul(&rhs.t())
893 }
894
895 pub fn attention_masked(
901 &self,
902 k: &Tensor,
903 v: &Tensor,
904 mask: &Tensor,
905 num_heads: usize,
906 head_dim: usize,
907 ) -> Self {
908 let k_id = self.adopt(k);
909 let v_id = self.adopt(v);
910 let m_id = self.adopt(mask);
911 let id = self.handle.with_graph(|g| {
912 let s = rlx_ir::shape::attention_shape(g.shape(self.id));
913 g.attention(self.id, k_id, v_id, m_id, num_heads, head_dim, s)
914 });
915 Self::new(self.handle.clone(), id)
916 }
917
918 pub fn rope(&self, cos: &Tensor, sin: &Tensor, head_dim: usize) -> Self {
919 let cos_id = self.adopt(cos);
920 let sin_id = self.adopt(sin);
921 let id = self
922 .handle
923 .with_graph(|g| g.rope(self.id, cos_id, sin_id, head_dim));
924 Self::new(self.handle.clone(), id)
925 }
926
927 pub fn where_<'t, 'f>(
932 &self,
933 on_true: impl Into<BinaryRhs<'t>>,
934 on_false: impl Into<BinaryRhs<'f>>,
935 ) -> Self {
936 let t = on_true.into();
937 let f = on_false.into();
938 let hint = if let BinaryRhs::Tensor(x) = &t {
939 x.dtype()
940 } else if let BinaryRhs::Tensor(x) = &f {
941 x.dtype()
942 } else {
943 DType::F32
944 };
945 let t_id = self.where_branch(t, hint);
946 let f_id = self.where_branch(f, hint);
947 let id = self.handle.with_graph(|g| {
948 let s = rlx_ir::shape::binary_shape(g.shape(t_id), g.shape(f_id))
949 .expect("where shape inference");
950 g.add_node(Op::Where, vec![self.id, t_id, f_id], s)
951 });
952 Self::new(self.handle.clone(), id)
953 }
954
955 fn where_branch(&self, rhs: BinaryRhs<'_>, hint: DType) -> NodeId {
958 match rhs {
959 BinaryRhs::Tensor(t) => self.adopt(t),
960 BinaryRhs::Scalar(s) => self.handle.with_graph(|g| promote_scalar(g, s, hint)),
961 }
962 }
963
964 pub fn masked_fill(&self, mask: &Tensor, value: f64) -> Self {
968 let dims: Vec<usize> = self
969 .shape()
970 .dims()
971 .iter()
972 .map(|d| match d {
973 Dim::Static(n) => *n,
974 Dim::Dynamic(_) => panic!("masked_fill requires a static shape"),
975 })
976 .collect();
977 let fill = Tensor::full(dims, value as f32);
978 mask.where_(&fill, self)
979 }
980
981 pub fn maximum<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
983 self.map_binary_op(rhs.into(), BinaryOp::Max)
984 }
985
986 pub fn minimum<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
988 self.map_binary_op(rhs.into(), BinaryOp::Min)
989 }
990
991 pub fn atan2<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
993 self.map_binary_op(rhs.into(), BinaryOp::Atan2)
994 }
995
996 pub fn rem<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
998 self.map_binary_op(rhs.into(), BinaryOp::Mod)
999 }
1000
1001 pub fn clamp(&self, min: f64, max: f64) -> Self {
1003 self.clamp_min(min).clamp_max(max)
1004 }
1005
1006 pub fn clamp_min(&self, min: f64) -> Self {
1008 self.map_binary_op(BinaryRhs::Scalar(min.into()), BinaryOp::Max)
1009 }
1010
1011 pub fn clamp_max(&self, max: f64) -> Self {
1013 self.map_binary_op(BinaryRhs::Scalar(max.into()), BinaryOp::Min)
1014 }
1015
1016 pub fn pow<'r>(&self, rhs: impl Into<BinaryRhs<'r>>) -> Self {
1018 self.map_binary_op(rhs.into(), BinaryOp::Pow)
1019 }
1020
1021 pub fn pow_scalar(&self, exp: f64) -> Self {
1022 self.map_binary_op(BinaryRhs::Scalar(exp.into()), BinaryOp::Pow)
1023 }
1024}
1025
1026unary_ops! {
1027 relu => Relu,
1029 gelu => Gelu,
1031 gelu_approx => GeluApprox,
1033 silu => Silu,
1035 tanh => Tanh,
1037 exp => Exp,
1039 sqrt => Sqrt,
1041 sigmoid => Sigmoid,
1043 log => Log,
1045 rsqrt => Rsqrt,
1047 abs => Abs,
1049 sin => Sin,
1051 cos => Cos,
1053 tan => Tan,
1055 atan => Atan,
1057 round => Round,
1059 recip => Recip,
1061 floor => Floor,
1063 ceil => Ceil,
1065 sign => Sign,
1067 softplus => Softplus,
1069 elu => Elu,
1071 erf => Erf,
1073 hard_swish => HardSwish,
1075 hard_sigmoid => HardSigmoid,
1077 mish => Mish,
1079 softsign => Softsign,
1081 log_sigmoid => LogSigmoid,
1083}
1084
1085cmp_ops! {
1086 eq => Eq,
1088 ne => Ne,
1090 lt => Lt,
1092 le => Le,
1094 gt => Gt,
1096 ge => Ge,
1098}
1099
1100reduce_ops! {
1101 sum => Sum,
1103 mean => Mean,
1105 max => Max,
1107 min => Min,
1109 prod => Prod,
1111}
1112
1113#[cfg(feature = "eval")]
1117macro_rules! pod_readers {
1118 ($($(#[$m:meta])* $name:ident => ($ty:ty, $dt:ident, $width:literal)),+ $(,)?) => {
1119 $(
1120 $(#[$m])*
1121 pub fn $name(&self) -> Vec<$ty> {
1122 let (bytes, dt) = self.eval_typed();
1123 assert_eq!(
1124 dt, DType::$dt,
1125 concat!(stringify!($name), ": tensor dtype is {:?}"), dt,
1126 );
1127 bytes
1128 .chunks_exact($width)
1129 .map(|c| <$ty>::from_le_bytes(c.try_into().unwrap()))
1130 .collect()
1131 }
1132 )+
1133 };
1134}
1135
1136#[cfg(feature = "eval")]
1141impl Tensor {
1142 pub fn to_vec(&self) -> Vec<f32> {
1146 self.to_vec_on(self.auto_device())
1147 }
1148
1149 pub fn to_vec_on(&self, device: rlx_runtime::Device) -> Vec<f32> {
1151 if let Some((bytes, DType::F32)) = self.constant_payload() {
1156 return bytes
1157 .chunks_exact(4)
1158 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
1159 .collect();
1160 }
1161 let compiled = self.compiled_on(device);
1162 let mut outputs = compiled.borrow_mut().run(&[]);
1163 outputs.pop().expect("eval produced no output")
1164 }
1165
1166 fn constant_payload(&self) -> Option<(Vec<u8>, DType)> {
1171 self.handle.with_graph(|g| {
1172 let node = g.node(self.id);
1173 if let Op::Constant { data } = &node.op {
1174 if node.shape.size_bytes() == Some(data.len()) {
1175 return Some((data.clone(), node.shape.dtype()));
1176 }
1177 }
1178 None
1179 })
1180 }
1181
1182 fn auto_device(&self) -> rlx_runtime::Device {
1184 self.handle
1185 .with_graph(|g| rlx_runtime::fastest_device_for(g))
1186 }
1187
1188 fn compiled_on(
1191 &self,
1192 device: rlx_runtime::Device,
1193 ) -> std::rc::Rc<std::cell::RefCell<rlx_runtime::CompiledGraph>> {
1194 self.handle
1195 .with_graph(|g| crate::cache::compiled_output(g, self.id, device))
1196 }
1197
1198 pub fn realize(&self) -> Vec<f32> {
1204 self.to_vec()
1205 }
1206
1207 pub fn realize_on(&self, device: rlx_runtime::Device) -> Vec<f32> {
1209 self.to_vec_on(device)
1210 }
1211
1212 pub fn item(&self) -> f32 {
1214 let v = self.to_vec();
1215 assert_eq!(v.len(), 1, "item(): tensor has {} elements, not 1", v.len());
1216 v[0]
1217 }
1218
1219 pod_readers! {
1220 to_vec_f64 => (f64, F64, 8),
1222 to_vec_i64 => (i64, I64, 8),
1224 to_vec_i32 => (i32, I32, 4),
1226 }
1227
1228 pub fn to_vec_bool(&self) -> Vec<bool> {
1232 let (bytes, dt) = self.eval_typed();
1233 assert_eq!(dt, DType::Bool, "to_vec_bool: tensor dtype is {dt:?}");
1234 let n = self.numel();
1235 assert!(n > 0 && bytes.len() % n == 0, "to_vec_bool: ragged output");
1236 let w = bytes.len() / n;
1237 bytes
1238 .chunks_exact(w)
1239 .map(|c| c.iter().any(|&b| b != 0))
1240 .collect()
1241 }
1242
1243 fn eval_typed(&self) -> (Vec<u8>, DType) {
1246 if let Some(ct) = self.constant_payload() {
1248 return ct;
1249 }
1250 let compiled = self.compiled_on(self.auto_device());
1251 let mut outputs = compiled.borrow_mut().run_typed(&[]);
1252 outputs.pop().expect("eval produced no output")
1253 }
1254
1255 pub fn show(&self) -> String {
1258 let dims = self.dims();
1259 let data = self.to_vec();
1260 format!(
1261 "Tensor{dims:?} {:?}\n{}",
1262 self.dtype(),
1263 fmt_nested(&data, &dims)
1264 )
1265 }
1266
1267 pub fn on(&self, device: rlx_runtime::Device) -> Materialize<'_> {
1269 Materialize {
1270 tensor: self,
1271 device,
1272 }
1273 }
1274}
1275
1276#[cfg(feature = "eval")]
1278pub struct Materialize<'a> {
1279 tensor: &'a Tensor,
1280 device: rlx_runtime::Device,
1281}
1282
1283#[cfg(feature = "eval")]
1284impl Materialize<'_> {
1285 pub fn to_vec(self) -> Vec<f32> {
1287 self.tensor.to_vec_on(self.device)
1288 }
1289}
1290
1291#[cfg(feature = "autodiff")]
1294impl Tensor {
1295 pub fn grad(&self, wrt: &[&Tensor]) -> Vec<Tensor> {
1309 let wrt_ids: Vec<NodeId> = wrt.iter().map(|t| self.adopt(t)).collect();
1313 let loss_id = self.id;
1314 let bwd = self.handle.with_graph(|g| {
1315 let mut forward = g.clone();
1316 forward.set_outputs(vec![loss_id]);
1317 let mut bwd = rlx_autodiff::grad(&forward, &wrt_ids);
1318 bake_unit_seed(&mut bwd);
1319 bwd
1320 });
1321 let handle = GraphHandle::new(bwd);
1322 let outputs = handle.with_graph(|g| g.outputs.clone());
1323 outputs
1324 .into_iter()
1325 .map(|id| Tensor::new(handle.clone(), id))
1326 .collect()
1327 }
1328}
1329
1330#[cfg(feature = "autodiff")]
1333pub(crate) fn bake_unit_seed(bwd: &mut rlx_ir::Graph) {
1334 let seed = bwd.nodes().iter().find_map(|n| match &n.op {
1335 Op::Input { name } if name == "d_output" => Some(n.id),
1336 _ => None,
1337 });
1338 if let Some(id) = seed {
1339 let n = bwd.node(id).shape.num_elements().unwrap_or(1);
1340 let data: Vec<u8> = (0..n).flat_map(|_| 1.0f32.to_le_bytes()).collect();
1341 let node = bwd.node_mut(id);
1342 node.op = Op::Constant { data };
1343 node.inputs = Vec::new();
1344 }
1345}
1346
1347impl From<Tensor> for NodeId {
1348 fn from(v: Tensor) -> Self {
1349 v.id
1350 }
1351}
1352
1353impl From<&Tensor> for NodeId {
1354 fn from(v: &Tensor) -> Self {
1355 v.id
1356 }
1357}
1358
1359pub(crate) enum BinaryRhs<'a> {
1360 Tensor(&'a Tensor),
1361 Scalar(Scalar),
1362}
1363
1364impl<'a> BinaryRhs<'a> {
1365 fn scalar(s: Scalar) -> Self {
1366 Self::Scalar(s)
1367 }
1368}
1369
1370impl<'a> From<&'a Tensor> for BinaryRhs<'a> {
1371 fn from(v: &'a Tensor) -> Self {
1372 Self::Tensor(v)
1373 }
1374}
1375
1376macro_rules! impl_scalar_rhs {
1377 ($($ty:ty),+ $(,)?) => {
1378 $(
1379 impl From<$ty> for BinaryRhs<'_> {
1380 fn from(v: $ty) -> Self {
1381 Self::Scalar(v.into())
1382 }
1383 }
1384 )+
1385 };
1386}
1387
1388impl_scalar_rhs!(bool, i32, i64, f32, f64);
1389
1390macro_rules! impl_tensor_binop {
1391 ($trait:ident, $method:ident, $graph:ident) => {
1392 impl $trait<&Tensor> for &Tensor {
1393 type Output = Tensor;
1394
1395 fn $method(self, rhs: &Tensor) -> Tensor {
1396 self.map_binary(BinaryRhs::Tensor(rhs), |g, a, b| g.$graph(a, b))
1397 }
1398 }
1399
1400 impl $trait<Tensor> for &Tensor {
1401 type Output = Tensor;
1402
1403 fn $method(self, rhs: Tensor) -> Tensor {
1404 self.$method(&rhs)
1405 }
1406 }
1407
1408 impl $trait<f64> for &Tensor {
1409 type Output = Tensor;
1410
1411 fn $method(self, rhs: f64) -> Tensor {
1412 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1413 }
1414 }
1415
1416 impl $trait<f32> for &Tensor {
1417 type Output = Tensor;
1418
1419 fn $method(self, rhs: f32) -> Tensor {
1420 self.$method(rhs as f64)
1421 }
1422 }
1423
1424 impl $trait<i32> for &Tensor {
1425 type Output = Tensor;
1426
1427 fn $method(self, rhs: i32) -> Tensor {
1428 self.map_binary(BinaryRhs::scalar(rhs.into()), |g, a, b| g.$graph(a, b))
1429 }
1430 }
1431
1432 impl $trait<&Tensor> for Tensor {
1433 type Output = Tensor;
1434
1435 fn $method(self, rhs: &Tensor) -> Tensor {
1436 (&self).$method(rhs)
1437 }
1438 }
1439
1440 impl $trait<Tensor> for Tensor {
1441 type Output = Tensor;
1442
1443 fn $method(self, rhs: Tensor) -> Tensor {
1444 (&self).$method(&rhs)
1445 }
1446 }
1447
1448 impl $trait<f64> for Tensor {
1449 type Output = Tensor;
1450
1451 fn $method(self, rhs: f64) -> Tensor {
1452 (&self).$method(rhs)
1453 }
1454 }
1455
1456 impl $trait<f32> for Tensor {
1457 type Output = Tensor;
1458
1459 fn $method(self, rhs: f32) -> Tensor {
1460 (&self).$method(rhs)
1461 }
1462 }
1463
1464 impl $trait<i32> for Tensor {
1465 type Output = Tensor;
1466
1467 fn $method(self, rhs: i32) -> Tensor {
1468 (&self).$method(rhs)
1469 }
1470 }
1471 };
1472}
1473
1474impl_tensor_binop!(Add, add, add);
1475impl_tensor_binop!(Sub, sub, sub);
1476impl_tensor_binop!(Mul, mul, mul);
1477impl_tensor_binop!(Div, div, div);
1478
1479impl Neg for &Tensor {
1480 type Output = Tensor;
1481
1482 fn neg(self) -> Tensor {
1483 self.map_unary(|g, x| g.neg(x))
1484 }
1485}
1486
1487macro_rules! impl_scalar_left {
1488 ($trait:ident, $method:ident, $graph:ident) => {
1489 impl $trait<&Tensor> for f64 {
1490 type Output = Tensor;
1491
1492 fn $method(self, rhs: &Tensor) -> Tensor {
1493 let id = rhs.handle.with_graph(|g| {
1494 let lhs_id = promote_scalar(g, self.into(), g.shape(rhs.id).dtype());
1495 g.$graph(lhs_id, rhs.id)
1496 });
1497 Tensor::new(rhs.handle.clone(), id)
1498 }
1499 }
1500
1501 impl $trait<&Tensor> for f32 {
1502 type Output = Tensor;
1503
1504 fn $method(self, rhs: &Tensor) -> Tensor {
1505 (self as f64).$method(rhs)
1506 }
1507 }
1508
1509 impl $trait<&Tensor> for i32 {
1510 type Output = Tensor;
1511
1512 fn $method(self, rhs: &Tensor) -> Tensor {
1513 (self as i64 as f64).$method(rhs)
1514 }
1515 }
1516 };
1517}
1518
1519impl_scalar_left!(Add, add, add);
1520impl_scalar_left!(Sub, sub, sub);
1521impl_scalar_left!(Mul, mul, mul);
1522impl_scalar_left!(Div, div, div);
1523
1524impl Neg for Tensor {
1525 type Output = Tensor;
1526
1527 fn neg(self) -> Tensor {
1528 (&self).neg()
1529 }
1530}