use std::sync::Arc;
use crate::autograd::{self, Backward, GradSlot, Node};
use super::Device;
use super::shape;
use super::storage::Storage;
#[derive(Clone)]
pub struct Tensor {
storage: Arc<Storage>,
shape: Vec<usize>,
device: Device,
node: Option<Node>,
}
impl Tensor {
pub fn from_vec(data: Vec<f32>, shape: &[usize]) -> Tensor {
assert_eq!(
data.len(),
shape::numel(shape),
"from_vec: {} values do not fill shape {shape:?}",
data.len()
);
Tensor::raw(Storage::Cpu(data), shape.to_vec(), Device::Cpu)
}
pub fn scalar(value: f32) -> Tensor {
Tensor::from_vec(vec![value], &[1])
}
pub(crate) fn raw(storage: Storage, shape: Vec<usize>, device: Device) -> Tensor {
Tensor::raw_shared(Arc::new(storage), shape, device)
}
pub(crate) fn raw_shared(storage: Arc<Storage>, shape: Vec<usize>, device: Device) -> Tensor {
Tensor {
storage,
shape,
device,
node: None,
}
}
pub(crate) fn leaf(
storage: Arc<Storage>,
shape: Vec<usize>,
device: Device,
slot: GradSlot,
) -> Tensor {
Tensor {
storage,
shape,
device,
node: Some(Node::Leaf(slot)),
}
}
pub fn shape(&self) -> &[usize] {
&self.shape
}
pub fn ndim(&self) -> usize {
self.shape.len()
}
pub fn numel(&self) -> usize {
shape::numel(&self.shape)
}
pub fn device(&self) -> Device {
self.device
}
pub fn is_cuda(&self) -> bool {
self.device.is_cuda()
}
pub fn dim(&self, axis: usize) -> usize {
assert!(
axis < self.ndim(),
"dim {axis} out of range for shape {:?}",
self.shape
);
self.shape[axis]
}
pub fn last_dim(&self) -> usize {
*self.shape.last().expect("tensor has no dimensions")
}
pub fn to_vec(&self) -> Vec<f32> {
self.storage.to_vec()
}
pub fn item(&self) -> f32 {
assert_eq!(
self.numel(),
1,
"item() needs one element, got shape {:?}",
self.shape
);
self.to_vec()[0]
}
pub fn as_slice(&self) -> Option<&[f32]> {
self.storage.as_slice()
}
pub(crate) fn storage(&self) -> &Storage {
&self.storage
}
pub(crate) fn storage_arc(&self) -> Arc<Storage> {
self.storage.clone()
}
pub fn to(&self, device: Device) -> Tensor {
if self.device == device {
return self.clone();
}
let moved = Tensor::raw(self.storage.to_device(device), self.shape.clone(), device);
let from = self.device;
moved.with_grad(&[self], || ToDevice { from })
}
pub fn cpu(&self) -> Tensor {
self.to(Device::Cpu)
}
pub fn cuda(&self) -> Tensor {
self.to(Device::cuda(0).expect("cuda: no device"))
}
pub fn grad_fn(&self) -> Option<&Node> {
self.node.as_ref()
}
pub fn detach(&self) -> Tensor {
Tensor {
storage: self.storage.clone(),
shape: self.shape.clone(),
device: self.device,
node: None,
}
}
pub fn backward(&self) {
autograd::backward(self);
}
pub(crate) fn with_grad<R>(mut self, inputs: &[&Tensor], rule: impl FnOnce() -> R) -> Tensor
where
R: Backward + 'static,
{
if autograd::is_enabled() && inputs.iter().any(|t| t.node.is_some()) {
let saved = inputs.iter().map(|t| (*t).clone()).collect();
self.node = Some(Node::op(rule(), saved));
}
self
}
}
struct ToDevice {
from: Device,
}
impl Backward for ToDevice {
fn backward(&self, grad: &Tensor) -> Vec<Tensor> {
vec![grad.to(self.from)]
}
fn name(&self) -> &'static str {
"ToDevice"
}
}