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(Debug, Clone)]
22pub struct Tensor {
23 storage: Arc<Storage>,
24 layout: Layout,
25 autograd: Option<Arc<AutogradMeta>>,
26}
27
28impl Tensor {
29 pub fn from_storage(storage: Arc<Storage>, layout: Layout) -> Result<Self> {
35 let needed = max_addressed(&layout);
36 let available = storage_len(&storage);
37 if needed > available {
38 return Err(Error::InvalidArgument {
39 op: "Tensor::from_storage",
40 detail: format!("layout addresses {needed} elements, storage holds {available}"),
41 });
42 }
43 Ok(Self {
44 storage,
45 layout,
46 autograd: None,
47 })
48 }
49
50 pub fn from_vec_f32(data: Vec<f32>, shape: impl Into<Shape>) -> Result<Self> {
54 let shape = shape.into();
55 let numel = checked_numel(&shape, "Tensor::from_vec_f32")?;
56 if data.len() != numel {
57 return Err(Error::ShapeMismatch {
58 expected: Shape::from([data.len()]),
59 got: shape,
60 op: "Tensor::from_vec_f32",
61 });
62 }
63 Ok(Self {
64 storage: Arc::new(Storage::from_f32_vec(data)),
65 layout: Layout::contiguous(shape),
66 autograd: None,
67 })
68 }
69
70 pub fn from_slice(data: &[f32], shape: impl Into<Shape>) -> Result<Self> {
72 Self::from_vec_f32(data.to_vec(), shape)
73 }
74
75 pub fn from_vec_i64(data: Vec<i64>, shape: impl Into<Shape>) -> Result<Self> {
77 let shape = shape.into();
78 let numel = checked_numel(&shape, "Tensor::from_vec_i64")?;
79 if data.len() != numel {
80 return Err(Error::ShapeMismatch {
81 expected: Shape::from([data.len()]),
82 got: shape,
83 op: "Tensor::from_vec_i64",
84 });
85 }
86 Ok(Self {
87 storage: Arc::new(Storage::from_i64_vec(data)),
88 layout: Layout::contiguous(shape),
89 autograd: None,
90 })
91 }
92
93 pub fn zeros(shape: impl Into<Shape>) -> Self {
95 let shape = shape.into();
96 let numel = shape.numel();
97 Self::from_vec_f32(vec![0.0; numel], shape).expect("lengths match by construction")
98 }
99
100 pub fn ones(shape: impl Into<Shape>) -> Self {
102 let shape = shape.into();
103 let numel = shape.numel();
104 Self::from_vec_f32(vec![1.0; numel], shape).expect("lengths match by construction")
105 }
106
107 pub fn full(shape: impl Into<Shape>, value: f32) -> Self {
109 let shape = shape.into();
110 let numel = shape.numel();
111 Self::from_vec_f32(vec![value; numel], shape).expect("lengths match by construction")
112 }
113
114 pub fn scalar(value: f32) -> Self {
116 Self::from_vec_f32(vec![value], Shape::from([])).expect("scalar always fits")
117 }
118
119 pub fn randn(shape: impl Into<Shape>) -> Self {
121 Self::randn_with_seed(shape, rand::random())
122 }
123
124 pub fn randn_with_seed(shape: impl Into<Shape>, seed: u64) -> Self {
127 let shape = shape.into();
128 let numel = shape.numel();
129 let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
130 let normal = rand_distr::StandardNormal;
131 let data: Vec<f32> = (0..numel).map(|_| normal.sample(&mut rng)).collect();
132 Self::from_vec_f32(data, shape).expect("lengths match by construction")
133 }
134
135 pub fn shape(&self) -> &Shape {
139 &self.layout.shape
140 }
141
142 pub fn dims(&self) -> &[usize] {
144 self.layout.shape.dims()
145 }
146
147 pub fn ndim(&self) -> usize {
149 self.layout.shape.ndim()
150 }
151
152 pub fn numel(&self) -> usize {
154 self.layout.shape.numel()
155 }
156
157 pub fn layout(&self) -> &Layout {
159 &self.layout
160 }
161
162 pub fn dtype(&self) -> DType {
164 self.storage.dtype()
165 }
166
167 pub fn device(&self) -> Device {
169 self.storage.device()
170 }
171
172 pub fn storage(&self) -> &Arc<Storage> {
174 &self.storage
175 }
176
177 pub fn get_f32(&self, index: &[usize]) -> Result<f32> {
184 let offset = self.layout.offset_of(index)?;
185 Ok(self.storage.cpu()?.f32s()?[offset])
186 }
187
188 pub fn get_i64(&self, index: &[usize]) -> Result<i64> {
190 let offset = self.layout.offset_of(index)?;
191 Ok(self.storage.cpu()?.i64s()?[offset])
192 }
193
194 pub fn to_vec_f32(&self) -> Result<Vec<f32>> {
197 let src = self.storage.cpu()?.f32s()?;
198 Ok(gather_logical(src, &self.layout))
199 }
200
201 pub fn to_vec_i64(&self) -> Result<Vec<i64>> {
203 let src = self.storage.cpu()?.i64s()?;
204 Ok(gather_logical(src, &self.layout))
205 }
206
207 fn view(&self, layout: Layout) -> Self {
210 Self {
211 storage: Arc::clone(&self.storage),
212 layout,
213 autograd: None,
214 }
215 }
216
217 pub fn reshape(&self, shape: impl Into<Shape>) -> Result<Self> {
220 let shape = shape.into();
221 if checked_numel(&shape, "reshape")? != self.numel() {
222 return Err(Error::ShapeMismatch {
223 expected: self.shape().clone(),
224 got: shape,
225 op: "reshape",
226 });
227 }
228 let base = if self.layout.is_contiguous() {
229 self.clone()
230 } else {
231 self.contiguous_data()?
232 };
233 let layout = Layout {
234 strides: contiguous_strides(&shape),
235 shape,
236 offset: base.layout.offset,
237 };
238 let out = base.view(layout);
239 Ok(crate::ops::record_view(self, out, ViewKind::Reshape))
240 }
241
242 pub fn permute(&self, perm: &[usize]) -> Result<Self> {
245 let n = self.ndim();
246 if perm.len() != n || {
247 let mut seen = vec![false; n];
248 perm.iter()
249 .any(|&p| p >= n || std::mem::replace(&mut seen[p], true))
250 } {
251 return Err(Error::InvalidArgument {
252 op: "permute",
253 detail: format!("{perm:?} is not a permutation of 0..{n}"),
254 });
255 }
256 let dims = self.dims();
257 let strides = self.layout.strides.values();
258 let new_dims: Vec<usize> = perm.iter().map(|&p| dims[p]).collect();
259 let new_strides: Vec<isize> = perm.iter().map(|&p| strides[p]).collect();
260 let layout = Layout {
261 shape: Shape::new(new_dims),
262 strides: Strides::new(new_strides),
263 offset: self.layout.offset,
264 };
265 let out = self.view(layout);
266 Ok(crate::ops::record_view(
267 self,
268 out,
269 ViewKind::Permute(perm.to_vec()),
270 ))
271 }
272
273 pub fn transpose(&self, d0: usize, d1: usize) -> Result<Self> {
275 let mut perm: Vec<usize> = (0..self.ndim()).collect();
276 if d0 >= perm.len() || d1 >= perm.len() {
277 return Err(Error::InvalidArgument {
278 op: "transpose",
279 detail: format!("dims ({d0}, {d1}) out of range for rank {}", perm.len()),
280 });
281 }
282 perm.swap(d0, d1);
283 self.permute(&perm)
284 }
285
286 pub fn t(&self) -> Result<Self> {
288 let n = self.ndim();
289 if n < 2 {
290 return Err(Error::InvalidArgument {
291 op: "t",
292 detail: format!("needs rank >= 2, got {n}"),
293 });
294 }
295 self.transpose(n - 2, n - 1)
296 }
297
298 pub fn narrow(&self, dim: usize, start: usize, len: usize) -> Result<Self> {
300 let dims = self.dims();
301 if dim >= dims.len() || start + len > dims[dim] {
302 return Err(Error::InvalidArgument {
303 op: "narrow",
304 detail: format!(
305 "dim {dim}, range {start}..{} against shape {:?}",
306 start + len,
307 self.shape()
308 ),
309 });
310 }
311 let mut new_dims = dims.to_vec();
312 new_dims[dim] = len;
313 let strides = self.layout.strides.values().to_vec();
314 let offset = (self.layout.offset as isize + start as isize * strides[dim]) as usize;
315 let layout = Layout {
316 shape: Shape::new(new_dims),
317 strides: Strides::new(strides),
318 offset,
319 };
320 let out = self.view(layout);
321 Ok(crate::ops::record_view(
322 self,
323 out,
324 ViewKind::Narrow { dim, start, len },
325 ))
326 }
327
328 pub fn slice(&self, dim: usize, range: std::ops::Range<usize>) -> Result<Self> {
330 let len = range.end.saturating_sub(range.start);
331 self.narrow(dim, range.start, len)
332 }
333
334 pub fn broadcast_to(&self, shape: impl Into<Shape>) -> Result<Self> {
336 let shape = shape.into();
337 checked_numel(&shape, "broadcast_to")?;
338 let layout = broadcast_layout(&self.layout, &shape)?;
339 let out = self.view(layout);
340 Ok(crate::ops::record_view(self, out, ViewKind::Broadcast))
341 }
342
343 pub fn broadcast_view(&self, shape: &Shape) -> Result<Self> {
346 checked_numel(shape, "broadcast_view")?;
347 let layout = broadcast_layout(&self.layout, shape)?;
348 Ok(self.view(layout))
349 }
350
351 pub fn unsqueeze(&self, dim: usize) -> Result<Self> {
353 let mut dims = self.dims().to_vec();
354 if dim > dims.len() {
355 return Err(Error::InvalidArgument {
356 op: "unsqueeze",
357 detail: format!("dim {dim} out of range for rank {}", dims.len()),
358 });
359 }
360 dims.insert(dim, 1);
361 let mut strides = self.layout.strides.values().to_vec();
362 strides.insert(dim, 0);
363 let layout = Layout {
364 shape: Shape::new(dims),
365 strides: Strides::new(strides),
366 offset: self.layout.offset,
367 };
368 let out = self.view(layout);
369 Ok(crate::ops::record_view(self, out, ViewKind::Reshape))
370 }
371
372 pub fn contiguous(&self) -> Result<Self> {
375 if self.layout.is_contiguous() && self.layout.offset == 0 {
376 return Ok(self.clone());
377 }
378 let out = self.contiguous_data()?;
379 Ok(crate::ops::record_view(self, out, ViewKind::Contiguous))
380 }
381
382 pub fn contiguous_untracked(&self) -> Result<Self> {
385 self.contiguous_data()
386 }
387
388 pub(crate) fn contiguous_data_crate(&self) -> Result<Self> {
390 self.contiguous_data()
391 }
392
393 pub(crate) fn contiguous_data(&self) -> Result<Self> {
395 match self.device() {
396 Device::Cpu => {
397 let shape = self.shape().clone();
398 match self.storage.cpu()? {
399 CpuStorage::F32(_) => Tensor::from_vec_f32(self.to_vec_f32()?, shape),
400 CpuStorage::I64(_) => Tensor::from_vec_i64(self.to_vec_i64()?, shape),
401 CpuStorage::U8(_) => Err(Error::UnsupportedDType {
402 dtype: DType::U8,
403 op: "contiguous",
404 }),
405 }
406 }
407 device => backend_for(device)?.contiguous(self),
408 }
409 }
410
411 pub fn requires_grad_(mut self, requires: bool) -> Self {
416 match (&self.autograd, requires) {
417 (Some(meta), _) if meta.grad_fn.is_none() => {
418 self.autograd = requires.then(|| {
420 Arc::new(AutogradMeta {
421 requires_grad: true,
422 grad: Mutex::new(None),
423 grad_fn: None,
424 })
425 });
426 }
427 (Some(_), true) => { }
428 (Some(_), false) => self.autograd = None,
429 (None, true) => {
430 self.autograd = Some(Arc::new(AutogradMeta {
431 requires_grad: true,
432 grad: Mutex::new(None),
433 grad_fn: None,
434 }));
435 }
436 (None, false) => {}
437 }
438 self
439 }
440
441 pub fn requires_grad(&self) -> bool {
449 self.autograd.as_ref().is_some_and(|m| m.requires_grad)
450 }
451
452 pub fn is_tracked(&self) -> bool {
468 self.autograd.is_some()
469 }
470
471 pub fn grad(&self) -> Option<Tensor> {
473 self.autograd
474 .as_ref()
475 .and_then(|m| m.grad.lock().expect("grad mutex poisoned").clone())
476 }
477
478 pub fn zero_grad(&self) {
480 if let Some(meta) = &self.autograd {
481 *meta.grad.lock().expect("grad mutex poisoned") = None;
482 }
483 }
484
485 pub fn detach(&self) -> Self {
487 Self {
488 storage: Arc::clone(&self.storage),
489 layout: self.layout.clone(),
490 autograd: None,
491 }
492 }
493
494 pub fn backward(&self) -> Result<()> {
499 if self.numel() != 1 {
500 return Err(Error::InvalidArgument {
501 op: "backward",
502 detail: format!(
503 "output has {} elements; seed a non-scalar with backward_with",
504 self.numel()
505 ),
506 });
507 }
508 let seed = Tensor::ones(self.shape().clone()).to_device(self.device())?;
513 self.backward_with(seed)
514 }
515
516 pub fn backward_with(&self, seed: Tensor) -> Result<()> {
518 let seed = if seed.device() == self.device() {
519 seed
520 } else {
521 seed.to_device(self.device())?
522 };
523 crate::autograd::run_backward(self, seed)
524 }
525
526 pub(crate) fn autograd_meta(&self) -> Option<Arc<AutogradMeta>> {
527 self.autograd.clone()
528 }
529
530 pub(crate) fn with_grad_fn(mut self, grad_fn: GradFn) -> Self {
532 self.autograd = Some(Arc::new(AutogradMeta {
533 requires_grad: false,
534 grad: Mutex::new(None),
535 grad_fn: Some(grad_fn),
536 }));
537 self
538 }
539}
540
541#[derive(Debug, Clone)]
543pub(crate) enum ViewKind {
544 Reshape,
546 Permute(Vec<usize>),
548 Narrow {
550 dim: usize,
552 start: usize,
554 len: usize,
556 },
557 Broadcast,
559 Contiguous,
561}
562
563fn storage_len(storage: &Storage) -> usize {
564 match storage.data() {
565 crate::storage::StorageData::Cpu(c) => c.len(),
566 #[cfg(target_os = "macos")]
567 crate::storage::StorageData::Metal(b) => {
568 b.buffer().length() as usize / storage.dtype().size_in_bytes()
569 }
570 crate::storage::StorageData::Opaque(b) => b.len(),
571 }
572}
573
574fn max_addressed(layout: &Layout) -> usize {
575 if layout.shape.numel() == 0 {
576 return 0;
577 }
578 let mut max = layout.offset as isize;
579 for (&d, &s) in layout.shape.dims().iter().zip(layout.strides.values()) {
580 if d > 1 && s > 0 {
581 max += (d as isize - 1) * s;
582 }
583 }
584 (max + 1) as usize
585}
586
587pub(crate) fn broadcast_layout(layout: &Layout, target: &Shape) -> Result<Layout> {
589 let src = layout.shape.dims();
590 let dst = target.dims();
591 if dst.len() < src.len() {
592 return Err(Error::BroadcastIncompatible {
593 lhs: layout.shape.clone(),
594 rhs: target.clone(),
595 });
596 }
597 let lead = dst.len() - src.len();
598 let mut strides = vec![0isize; dst.len()];
599 for i in 0..src.len() {
600 let (s, d) = (src[i], dst[lead + i]);
601 if s == d {
602 strides[lead + i] = layout.strides.values()[i];
603 } else if s == 1 {
604 strides[lead + i] = 0;
605 } else {
606 return Err(Error::BroadcastIncompatible {
607 lhs: layout.shape.clone(),
608 rhs: target.clone(),
609 });
610 }
611 }
612 Ok(Layout {
613 shape: target.clone(),
614 strides: Strides::new(strides),
615 offset: layout.offset,
616 })
617}
618
619pub(crate) fn gather_logical<T: Copy>(src: &[T], layout: &Layout) -> Vec<T> {
621 let numel = layout.shape.numel();
622 let mut out = Vec::with_capacity(numel);
623 if numel == 0 {
624 return out;
625 }
626 let dims = layout.shape.dims();
627 if layout.is_contiguous() {
628 let start = layout.offset;
629 out.extend_from_slice(&src[start..start + numel]);
630 return out;
631 }
632 let strides = layout.strides.values();
633 let ndim = dims.len();
638 let inner = dims[ndim - 1];
639 let inner_stride = strides[ndim - 1];
640 if inner > 0 && (inner_stride == 0 || inner_stride == 1) {
641 let outer = Layout {
642 shape: Shape::new(dims[..ndim - 1].to_vec()),
643 strides: Strides::new(strides[..ndim - 1].to_vec()),
644 offset: layout.offset,
645 };
646 let mut walker = crate::cpu_iter::OffsetWalker::at(&outer, 0);
647 for _ in 0..numel / inner {
648 let base = walker.next_offset();
649 if inner_stride == 1 {
650 out.extend_from_slice(&src[base..base + inner]);
651 } else {
652 out.resize(out.len() + inner, src[base]);
653 }
654 }
655 return out;
656 }
657 let mut index = vec![0usize; dims.len()];
658 let mut offset = layout.offset as isize;
659 loop {
660 out.push(src[offset as usize]);
661 let mut d = dims.len();
664 loop {
665 if d == 0 {
666 return out;
667 }
668 d -= 1;
669 index[d] += 1;
670 offset += strides[d];
671 if index[d] < dims[d] {
672 break;
673 }
674 offset -= dims[d] as isize * strides[d];
675 index[d] = 0;
676 }
677 if out.len() == numel {
678 return out;
679 }
680 }
681}
682
683fn checked_numel(shape: &Shape, op: &'static str) -> Result<usize> {
686 shape.checked_numel().ok_or_else(|| Error::InvalidArgument {
687 op,
688 detail: format!(
689 "shape {:?} has more elements than fit in usize",
690 shape.dims()
691 ),
692 })
693}