Add complete SDK implementations for accessing Synor Compute: JavaScript/TypeScript SDK (sdk/js/): - Full async/await API with TypeScript types - Tensor operations: matmul, conv2d, attention - Model inference with streaming support - WebSocket-based job monitoring - Browser and Node.js compatible Python SDK (sdk/python/): - Async/await with aiohttp - NumPy integration for tensors - Context managers for cleanup - Type hints throughout - PyPI-ready package structure Go SDK (sdk/go/): - Idiomatic Go with context support - Efficient binary tensor serialization - HTTP client with configurable timeouts - Zero external dependencies (stdlib only) All SDKs support: - Matrix multiplication (FP64 to INT4 precision) - Convolution operations (2D, 3D) - Flash attention - LLM inference - Spot pricing queries - Job polling and cancellation - Heterogeneous compute targeting (CPU/GPU/TPU/NPU/LPU)
153 lines
4.8 KiB
Python
153 lines
4.8 KiB
Python
"""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})"
|