1use std::sync::{Arc, Mutex};
5
6use oxmera_core::layout::contiguous_strides;
7use oxmera_core::{DType, Device, Error, Layout, Result, Shape, Strides};
8use rand::SeedableRng;
9use rand_distr::Distribution;
10
11use crate::autograd::{AutogradMeta, GradFn};
12use crate::backend::backend_for;
13use crate::storage::{CpuStorage, Storage};
14
15#[derive(Clone)]
22pub struct Tensor {
23 storage: Arc<Storage>,
24 layout: Layout,
25 autograd: Option<Arc<AutogradMeta>>,
26}
27
28impl std::fmt::Debug for Tensor {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.debug_struct("Tensor")
35 .field("shape", self.shape())
36 .field("dtype", &self.dtype())
37 .field("device", &self.device())
38 .field("requires_grad", &self.requires_grad())
39 .finish()
40 }
41}
42
43impl Tensor {
44 pub fn from_storage(storage: Arc<Storage>, layout: Layout) -> Result<Self> {
50 let available = storage_len(&storage) as isize;
51 let Some((lo, hi)) = addressed_bounds(&layout) else {
52 return Err(Error::InvalidArgument {
53 op: "Tensor::from_storage",
54 detail: "layout extents overflow the address space, so it cannot be proven \
55 in bounds"
56 .into(),
57 });
58 };
59 if lo < 0 || hi > available {
60 return Err(Error::InvalidArgument {
61 op: "Tensor::from_storage",
62 detail: format!(
63 "layout addresses [{lo}, {hi}) but storage holds {available} elements"
64 ),
65 });
66 }
67 Ok(Self {
68 storage,
69 layout,
70 autograd: None,
71 })
72 }
73
74 pub fn from_vec_f32(data: Vec<f32>, shape: impl Into<Shape>) -> Result<Self> {
78 let shape = shape.into();
79 let numel = checked_numel(&shape, "Tensor::from_vec_f32")?;
80 if data.len() != numel {
81 return Err(Error::ShapeMismatch {
82 expected: Shape::from([data.len()]),
83 got: shape,
84 op: "Tensor::from_vec_f32",
85 });
86 }
87 Ok(Self {
88 storage: Arc::new(Storage::from_f32_vec(data)),
89 layout: Layout::contiguous(shape),
90 autograd: None,
91 })
92 }
93
94 pub fn from_slice(data: &[f32], shape: impl Into<Shape>) -> Result<Self> {
96 Self::from_vec_f32(data.to_vec(), shape)
97 }
98
99 pub fn from_vec_f64(data: Vec<f64>, shape: impl Into<Shape>) -> Result<Self> {
103 let shape = shape.into();
104 let numel = checked_numel(&shape, "Tensor::from_vec_f64")?;
105 if data.len() != numel {
106 return Err(Error::ShapeMismatch {
107 expected: Shape::from([data.len()]),
108 got: shape,
109 op: "Tensor::from_vec_f64",
110 });
111 }
112 Ok(Self {
113 storage: Arc::new(Storage::from_f64_vec(data)),
114 layout: Layout::contiguous(shape),
115 autograd: None,
116 })
117 }
118
119 pub fn from_vec_i64(data: Vec<i64>, shape: impl Into<Shape>) -> Result<Self> {
121 let shape = shape.into();
122 let numel = checked_numel(&shape, "Tensor::from_vec_i64")?;
123 if data.len() != numel {
124 return Err(Error::ShapeMismatch {
125 expected: Shape::from([data.len()]),
126 got: shape,
127 op: "Tensor::from_vec_i64",
128 });
129 }
130 Ok(Self {
131 storage: Arc::new(Storage::from_i64_vec(data)),
132 layout: Layout::contiguous(shape),
133 autograd: None,
134 })
135 }
136
137 pub fn zeros(shape: impl Into<Shape>) -> Self {
144 Self::try_zeros(shape).expect("shape element count overflows usize")
145 }
146
147 pub fn try_zeros(shape: impl Into<Shape>) -> Result<Self> {
150 let shape = shape.into();
151 let numel = checked_numel(&shape, "Tensor::try_zeros")?;
152 Self::from_vec_f32(try_filled(numel, 0.0, "Tensor::try_zeros")?, shape)
153 }
154
155 pub fn ones(shape: impl Into<Shape>) -> Self {
161 Self::try_ones(shape).expect("shape element count overflows usize")
162 }
163
164 pub fn try_ones(shape: impl Into<Shape>) -> Result<Self> {
167 let shape = shape.into();
168 let numel = checked_numel(&shape, "Tensor::try_ones")?;
169 Self::from_vec_f32(try_filled(numel, 1.0, "Tensor::try_ones")?, shape)
170 }
171
172 pub fn full(shape: impl Into<Shape>, value: f32) -> Self {
178 Self::try_full(shape, value).expect("shape element count overflows usize")
179 }
180
181 pub fn try_full(shape: impl Into<Shape>, value: f32) -> Result<Self> {
184 let shape = shape.into();
185 let numel = checked_numel(&shape, "Tensor::try_full")?;
186 Self::from_vec_f32(try_filled(numel, value, "Tensor::try_full")?, shape)
187 }
188
189 pub fn scalar(value: f32) -> Self {
191 Self::from_vec_f32(vec![value], Shape::from([])).expect("scalar always fits")
192 }
193
194 pub fn randn(shape: impl Into<Shape>) -> Self {
196 Self::randn_with_seed(shape, rand::random())
197 }
198
199 pub fn randn_with_seed(shape: impl Into<Shape>, seed: u64) -> Self {
202 let shape = shape.into();
203 let numel = shape.numel();
204 let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
205 let normal = rand_distr::StandardNormal;
206 let data: Vec<f32> = (0..numel).map(|_| normal.sample(&mut rng)).collect();
207 Self::from_vec_f32(data, shape).expect("lengths match by construction")
208 }
209
210 pub fn shape(&self) -> &Shape {
214 &self.layout.shape
215 }
216
217 pub fn dims(&self) -> &[usize] {
219 self.layout.shape.dims()
220 }
221
222 pub fn ndim(&self) -> usize {
224 self.layout.shape.ndim()
225 }
226
227 pub fn numel(&self) -> usize {
229 self.layout.shape.numel()
230 }
231
232 pub fn layout(&self) -> &Layout {
234 &self.layout
235 }
236
237 pub fn dtype(&self) -> DType {
239 self.storage.dtype()
240 }
241
242 pub fn device(&self) -> Device {
244 self.storage.device()
245 }
246
247 pub fn storage(&self) -> &Arc<Storage> {
249 &self.storage
250 }
251
252 pub fn get_f32(&self, index: &[usize]) -> Result<f32> {
259 let offset = self.layout.offset_of(index)?;
260 Ok(self.storage.cpu()?.f32s()?[offset])
261 }
262
263 pub fn get_f64(&self, index: &[usize]) -> Result<f64> {
265 let offset = self.layout.offset_of(index)?;
266 Ok(self.storage.cpu()?.f64s()?[offset])
267 }
268
269 pub fn get_i64(&self, index: &[usize]) -> Result<i64> {
271 let offset = self.layout.offset_of(index)?;
272 Ok(self.storage.cpu()?.i64s()?[offset])
273 }
274
275 pub fn to_vec_f32(&self) -> Result<Vec<f32>> {
278 let src = self.storage.cpu()?.f32s()?;
279 Ok(gather_logical(src, &self.layout))
280 }
281
282 pub fn to_vec_f64(&self) -> Result<Vec<f64>> {
285 let src = self.storage.cpu()?.f64s()?;
286 Ok(gather_logical(src, &self.layout))
287 }
288
289 pub fn to_vec_i64(&self) -> Result<Vec<i64>> {
291 let src = self.storage.cpu()?.i64s()?;
292 Ok(gather_logical(src, &self.layout))
293 }
294
295 fn view(&self, layout: Layout) -> Self {
298 Self {
299 storage: Arc::clone(&self.storage),
300 layout,
301 autograd: None,
302 }
303 }
304
305 pub fn reshape(&self, shape: impl Into<Shape>) -> Result<Self> {
308 let shape = shape.into();
309 if checked_numel(&shape, "reshape")? != self.numel() {
310 return Err(Error::ShapeMismatch {
311 expected: self.shape().clone(),
312 got: shape,
313 op: "reshape",
314 });
315 }
316 let base = if self.layout.is_contiguous() {
317 self.clone()
318 } else {
319 self.contiguous_data()?
320 };
321 let layout = Layout {
322 strides: contiguous_strides(&shape),
323 shape,
324 offset: base.layout.offset,
325 };
326 let out = base.view(layout);
327 Ok(crate::ops::record_view(self, out, ViewKind::Reshape))
328 }
329
330 pub fn permute(&self, perm: &[usize]) -> Result<Self> {
333 let n = self.ndim();
334 if perm.len() != n || {
335 let mut seen = vec![false; n];
336 perm.iter()
337 .any(|&p| p >= n || std::mem::replace(&mut seen[p], true))
338 } {
339 return Err(Error::InvalidArgument {
340 op: "permute",
341 detail: format!("{perm:?} is not a permutation of 0..{n}"),
342 });
343 }
344 let dims = self.dims();
345 let strides = self.layout.strides.values();
346 let new_dims: Vec<usize> = perm.iter().map(|&p| dims[p]).collect();
347 let new_strides: Vec<isize> = perm.iter().map(|&p| strides[p]).collect();
348 let layout = Layout {
349 shape: Shape::new(new_dims),
350 strides: Strides::new(new_strides),
351 offset: self.layout.offset,
352 };
353 let out = self.view(layout);
354 Ok(crate::ops::record_view(
355 self,
356 out,
357 ViewKind::Permute(perm.to_vec()),
358 ))
359 }
360
361 pub fn transpose(&self, d0: usize, d1: usize) -> Result<Self> {
363 let mut perm: Vec<usize> = (0..self.ndim()).collect();
364 if d0 >= perm.len() || d1 >= perm.len() {
365 return Err(Error::InvalidArgument {
366 op: "transpose",
367 detail: format!("dims ({d0}, {d1}) out of range for rank {}", perm.len()),
368 });
369 }
370 perm.swap(d0, d1);
371 self.permute(&perm)
372 }
373
374 pub fn t(&self) -> Result<Self> {
376 let n = self.ndim();
377 if n < 2 {
378 return Err(Error::InvalidArgument {
379 op: "t",
380 detail: format!("needs rank >= 2, got {n}"),
381 });
382 }
383 self.transpose(n - 2, n - 1)
384 }
385
386 pub fn narrow(&self, dim: usize, start: usize, len: usize) -> Result<Self> {
388 let dims = self.dims();
389 let end = start.checked_add(len);
390 if dim >= dims.len() || end.is_none_or(|e| e > dims[dim]) {
391 return Err(Error::InvalidArgument {
392 op: "narrow",
393 detail: format!(
394 "dim {dim}, start {start} len {len} against shape {:?}",
395 self.shape()
396 ),
397 });
398 }
399 let mut new_dims = dims.to_vec();
400 new_dims[dim] = len;
401 let strides = self.layout.strides.values().to_vec();
402 let offset = (self.layout.offset as isize + start as isize * strides[dim]) as usize;
403 let layout = Layout {
404 shape: Shape::new(new_dims),
405 strides: Strides::new(strides),
406 offset,
407 };
408 let out = self.view(layout);
409 Ok(crate::ops::record_view(
410 self,
411 out,
412 ViewKind::Narrow { dim, start, len },
413 ))
414 }
415
416 pub fn slice(&self, dim: usize, range: std::ops::Range<usize>) -> Result<Self> {
418 if range.end < range.start {
419 return Err(Error::InvalidArgument {
422 op: "slice",
423 detail: format!("range {}..{} is reversed", range.start, range.end),
424 });
425 }
426 self.narrow(dim, range.start, range.end - range.start)
427 }
428
429 pub fn broadcast_to(&self, shape: impl Into<Shape>) -> Result<Self> {
431 let shape = shape.into();
432 checked_numel(&shape, "broadcast_to")?;
433 let layout = broadcast_layout(&self.layout, &shape)?;
434 let out = self.view(layout);
435 Ok(crate::ops::record_view(self, out, ViewKind::Broadcast))
436 }
437
438 pub fn broadcast_view(&self, shape: &Shape) -> Result<Self> {
441 checked_numel(shape, "broadcast_view")?;
442 let layout = broadcast_layout(&self.layout, shape)?;
443 Ok(self.view(layout))
444 }
445
446 pub fn unsqueeze(&self, dim: usize) -> Result<Self> {
448 let mut dims = self.dims().to_vec();
449 if dim > dims.len() {
450 return Err(Error::InvalidArgument {
451 op: "unsqueeze",
452 detail: format!("dim {dim} out of range for rank {}", dims.len()),
453 });
454 }
455 dims.insert(dim, 1);
456 let mut strides = self.layout.strides.values().to_vec();
457 strides.insert(dim, 0);
458 let layout = Layout {
459 shape: Shape::new(dims),
460 strides: Strides::new(strides),
461 offset: self.layout.offset,
462 };
463 let out = self.view(layout);
464 Ok(crate::ops::record_view(self, out, ViewKind::Reshape))
465 }
466
467 pub fn contiguous(&self) -> Result<Self> {
470 if self.layout.is_contiguous() && self.layout.offset == 0 {
471 return Ok(self.clone());
472 }
473 let out = self.contiguous_data()?;
474 Ok(crate::ops::record_view(self, out, ViewKind::Contiguous))
475 }
476
477 pub fn contiguous_untracked(&self) -> Result<Self> {
480 self.contiguous_data()
481 }
482
483 pub(crate) fn contiguous_data_crate(&self) -> Result<Self> {
485 self.contiguous_data()
486 }
487
488 pub(crate) fn contiguous_data(&self) -> Result<Self> {
490 match self.device() {
491 Device::Cpu => {
492 let shape = self.shape().clone();
493 match self.storage.cpu()? {
494 CpuStorage::F32(_) => Tensor::from_vec_f32(self.to_vec_f32()?, shape),
495 CpuStorage::F64(_) => Tensor::from_vec_f64(self.to_vec_f64()?, shape),
496 CpuStorage::I64(_) => Tensor::from_vec_i64(self.to_vec_i64()?, shape),
497 CpuStorage::U8(_) => Err(Error::UnsupportedDType {
498 dtype: DType::U8,
499 op: "contiguous",
500 }),
501 }
502 }
503 device => backend_for(device)?.contiguous(self),
504 }
505 }
506
507 pub fn requires_grad_(mut self, requires: bool) -> Self {
512 match (&self.autograd, requires) {
513 (Some(meta), _) if meta.grad_fn.is_none() => {
514 self.autograd = requires.then(|| {
516 Arc::new(AutogradMeta {
517 requires_grad: true,
518 grad: Mutex::new(None),
519 grad_fn: None,
520 })
521 });
522 }
523 (Some(_), true) => { }
524 (Some(_), false) => self.autograd = None,
525 (None, true) => {
526 self.autograd = Some(Arc::new(AutogradMeta {
527 requires_grad: true,
528 grad: Mutex::new(None),
529 grad_fn: None,
530 }));
531 }
532 (None, false) => {}
533 }
534 self
535 }
536
537 pub fn requires_grad(&self) -> bool {
545 self.autograd.as_ref().is_some_and(|m| m.requires_grad)
546 }
547
548 pub fn is_tracked(&self) -> bool {
564 self.autograd.is_some()
565 }
566
567 pub fn grad(&self) -> Option<Tensor> {
569 self.autograd
570 .as_ref()
571 .and_then(|m| m.grad.lock().expect("grad mutex poisoned").clone())
572 }
573
574 pub fn zero_grad(&self) {
576 if let Some(meta) = &self.autograd {
577 *meta.grad.lock().expect("grad mutex poisoned") = None;
578 }
579 }
580
581 pub fn detach(&self) -> Self {
583 Self {
584 storage: Arc::clone(&self.storage),
585 layout: self.layout.clone(),
586 autograd: None,
587 }
588 }
589
590 pub fn backward(&self) -> Result<()> {
595 if self.numel() != 1 {
596 return Err(Error::InvalidArgument {
597 op: "backward",
598 detail: format!(
599 "output has {} elements; seed a non-scalar with backward_with",
600 self.numel()
601 ),
602 });
603 }
604 let seed = Tensor::ones(self.shape().clone())
609 .to_dtype(self.dtype())?
610 .to_device(self.device())?;
611 self.backward_with(seed)
612 }
613
614 pub fn backward_with(&self, seed: Tensor) -> Result<()> {
616 let seed = if seed.device() == self.device() {
617 seed
618 } else {
619 seed.to_device(self.device())?
620 };
621 crate::autograd::run_backward(self, seed)
622 }
623
624 pub(crate) fn autograd_meta(&self) -> Option<Arc<AutogradMeta>> {
625 self.autograd.clone()
626 }
627
628 pub(crate) fn take_autograd(&mut self) -> Option<Arc<AutogradMeta>> {
633 self.autograd.take()
634 }
635
636 pub(crate) fn with_grad_fn(mut self, grad_fn: GradFn) -> Self {
637 self.autograd = Some(Arc::new(AutogradMeta {
638 requires_grad: false,
639 grad: Mutex::new(None),
640 grad_fn: Some(grad_fn),
641 }));
642 self
643 }
644}
645
646#[derive(Debug, Clone)]
648pub(crate) enum ViewKind {
649 Reshape,
651 Permute(Vec<usize>),
653 Narrow {
655 dim: usize,
657 start: usize,
659 len: usize,
661 },
662 Broadcast,
664 Contiguous,
666}
667
668fn storage_len(storage: &Storage) -> usize {
669 match storage.data() {
670 crate::storage::StorageData::Cpu(c) => c.len(),
671 #[cfg(target_os = "macos")]
672 crate::storage::StorageData::Metal(b) => {
673 b.buffer().length() as usize / storage.dtype().size_in_bytes()
674 }
675 crate::storage::StorageData::Opaque(b) => b.len(),
676 }
677}
678
679fn addressed_bounds(layout: &Layout) -> Option<(isize, isize)> {
686 if layout.shape.checked_numel()? == 0 {
687 return Some((0, 0));
688 }
689 let base = isize::try_from(layout.offset).ok()?;
690 let mut lo = base;
691 let mut hi = base;
692 for (&d, &s) in layout.shape.dims().iter().zip(layout.strides.values()) {
693 if d > 1 {
694 let span = isize::try_from(d - 1).ok()?.checked_mul(s)?;
695 if s >= 0 {
696 hi = hi.checked_add(span)?;
697 } else {
698 lo = lo.checked_add(span)?;
699 }
700 }
701 }
702 Some((lo, hi.checked_add(1)?))
703}
704
705pub(crate) fn broadcast_layout(layout: &Layout, target: &Shape) -> Result<Layout> {
707 let src = layout.shape.dims();
708 let dst = target.dims();
709 if dst.len() < src.len() {
710 return Err(Error::BroadcastIncompatible {
711 lhs: layout.shape.clone(),
712 rhs: target.clone(),
713 });
714 }
715 let lead = dst.len() - src.len();
716 let mut strides = vec![0isize; dst.len()];
717 for i in 0..src.len() {
718 let (s, d) = (src[i], dst[lead + i]);
719 if s == d {
720 strides[lead + i] = layout.strides.values()[i];
721 } else if s == 1 {
722 strides[lead + i] = 0;
723 } else {
724 return Err(Error::BroadcastIncompatible {
725 lhs: layout.shape.clone(),
726 rhs: target.clone(),
727 });
728 }
729 }
730 Ok(Layout {
731 shape: target.clone(),
732 strides: Strides::new(strides),
733 offset: layout.offset,
734 })
735}
736
737pub(crate) fn gather_logical<T: Copy>(src: &[T], layout: &Layout) -> Vec<T> {
739 let numel = layout.shape.numel();
740 let mut out = Vec::with_capacity(numel);
741 if numel == 0 {
742 return out;
743 }
744 let dims = layout.shape.dims();
745 if layout.is_contiguous() {
746 let start = layout.offset;
747 out.extend_from_slice(&src[start..start + numel]);
748 return out;
749 }
750 let strides = layout.strides.values();
751 let ndim = dims.len();
756 let inner = dims[ndim - 1];
757 let inner_stride = strides[ndim - 1];
758 if inner > 0 && (inner_stride == 0 || inner_stride == 1) {
759 let outer = Layout {
760 shape: Shape::new(dims[..ndim - 1].to_vec()),
761 strides: Strides::new(strides[..ndim - 1].to_vec()),
762 offset: layout.offset,
763 };
764 let mut walker = crate::cpu_iter::OffsetWalker::at(&outer, 0);
765 for _ in 0..numel / inner {
766 let base = walker.next_offset();
767 if inner_stride == 1 {
768 out.extend_from_slice(&src[base..base + inner]);
769 } else {
770 out.resize(out.len() + inner, src[base]);
771 }
772 }
773 return out;
774 }
775 let mut index = vec![0usize; dims.len()];
776 let mut offset = layout.offset as isize;
777 loop {
778 out.push(src[offset as usize]);
779 let mut d = dims.len();
782 loop {
783 if d == 0 {
784 return out;
785 }
786 d -= 1;
787 index[d] += 1;
788 offset += strides[d];
789 if index[d] < dims[d] {
790 break;
791 }
792 offset -= dims[d] as isize * strides[d];
793 index[d] = 0;
794 }
795 if out.len() == numel {
796 return out;
797 }
798 }
799}
800
801fn try_filled(numel: usize, value: f32, op: &'static str) -> Result<Vec<f32>> {
808 let mut data: Vec<f32> = Vec::new();
809 data.try_reserve_exact(numel)
810 .map_err(|_| Error::InvalidArgument {
811 op,
812 detail: format!("cannot allocate {numel} f32 elements"),
813 })?;
814 data.resize(numel, value);
815 Ok(data)
816}
817
818fn checked_numel(shape: &Shape, op: &'static str) -> Result<usize> {
819 shape.checked_numel().ok_or_else(|| Error::InvalidArgument {
820 op,
821 detail: format!(
822 "shape {:?} has more elements than fit in usize",
823 shape.dims()
824 ),
825 })
826}