"""Tensor utilities for Synor Compute SDK.""" from typing import Any import base64 import numpy as np from .types import Precision class Tensor: """ Tensor wrapper for compute operations. Example: >>> tensor = Tensor.from_numpy(np.random.randn(1024, 1024)) >>> tensor = Tensor.zeros((1024, 1024), dtype=Precision.FP16) """ def __init__( self, data: np.ndarray, dtype: Precision = Precision.FP32, ): self._data = data self._dtype = dtype @classmethod def from_numpy(cls, array: np.ndarray, dtype: Precision | None = None) -> "Tensor": """Create a tensor from a numpy array.""" if dtype is None: dtype = cls._infer_dtype(array.dtype) return cls(array, dtype) @classmethod def zeros(cls, shape: tuple[int, ...], dtype: Precision = Precision.FP32) -> "Tensor": """Create a tensor of zeros.""" np_dtype = cls._to_numpy_dtype(dtype) return cls(np.zeros(shape, dtype=np_dtype), dtype) @classmethod def ones(cls, shape: tuple[int, ...], dtype: Precision = Precision.FP32) -> "Tensor": """Create a tensor of ones.""" np_dtype = cls._to_numpy_dtype(dtype) return cls(np.ones(shape, dtype=np_dtype), dtype) @classmethod def random(cls, shape: tuple[int, ...], dtype: Precision = Precision.FP32) -> "Tensor": """Create a tensor with random values.""" np_dtype = cls._to_numpy_dtype(dtype) return cls(np.random.random(shape).astype(np_dtype), dtype) @classmethod def randn(cls, shape: tuple[int, ...], dtype: Precision = Precision.FP32) -> "Tensor": """Create a tensor with normal distribution.""" np_dtype = cls._to_numpy_dtype(dtype) return cls(np.random.randn(*shape).astype(np_dtype), dtype) @property def data(self) -> np.ndarray: """Get the underlying numpy array.""" return self._data @property def shape(self) -> tuple[int, ...]: """Get tensor shape.""" return self._data.shape @property def dtype(self) -> Precision: """Get tensor dtype.""" return self._dtype @property def size(self) -> int: """Get total number of elements.""" return self._data.size @property def ndim(self) -> int: """Get number of dimensions.""" return self._data.ndim @property def nbytes(self) -> int: """Get byte size.""" return self._data.nbytes def reshape(self, shape: tuple[int, ...]) -> "Tensor": """Reshape tensor.""" return Tensor(self._data.reshape(shape), self._dtype) def to(self, dtype: Precision) -> "Tensor": """Convert to different precision.""" if dtype == self._dtype: return self np_dtype = self._to_numpy_dtype(dtype) return Tensor(self._data.astype(np_dtype), dtype) def numpy(self) -> np.ndarray: """Convert to numpy array.""" return self._data def serialize(self) -> dict[str, Any]: """Serialize tensor for transmission.""" data_bytes = self._data.tobytes() data_b64 = base64.b64encode(data_bytes).decode("ascii") return { "data": data_b64, "shape": list(self._data.shape), "dtype": self._dtype.value, } @classmethod def deserialize(cls, data: dict[str, Any]) -> "Tensor": """Deserialize tensor from transmission format.""" data_bytes = base64.b64decode(data["data"]) dtype = Precision(data["dtype"]) np_dtype = cls._to_numpy_dtype(dtype) array = np.frombuffer(data_bytes, dtype=np_dtype).reshape(data["shape"]) return cls(array, dtype) @staticmethod def _infer_dtype(np_dtype: np.dtype) -> Precision: """Infer Precision from numpy dtype.""" if np_dtype == np.float64: return Precision.FP64 elif np_dtype == np.float32: return Precision.FP32 elif np_dtype == np.float16: return Precision.FP16 elif np_dtype == np.int8: return Precision.INT8 else: return Precision.FP32 @staticmethod def _to_numpy_dtype(dtype: Precision) -> np.dtype: """Convert Precision to numpy dtype.""" mapping = { Precision.FP64: np.float64, Precision.FP32: np.float32, Precision.FP16: np.float16, Precision.BF16: np.float16, # Approximate Precision.INT8: np.int8, Precision.INT4: np.int8, # Approximate } return np.dtype(mapping.get(dtype, np.float32)) def __repr__(self) -> str: return f"Tensor(shape={self.shape}, dtype={self._dtype.value})" def __str__(self) -> str: return f"Tensor({self._data}, dtype={self._dtype.value})"