Packages
Tensor library for Gleam/BEAM with a pure Gleam API, zero-copy views, and optional native acceleration
Retired package: Release invalid
Current section
Files
Jump to
Current section
Files
src/viva_tensor.gleam
//// viva_tensor - NumPy for the BEAM.
////
//// Born from the frustration of "why can't I do tensor math in Erlang/Elixir
//// without calling Python?" Now you can.
////
//// The name: "viva" = alive in Portuguese/Spanish. Tensors that live on the BEAM.
//// Also, "viva" sounds better than "gleam_tensor" (sorry, marketing decision).
////
//// Architecture:
//// - core/ = the fundamentals (tensor, ops, shape, error)
//// - nn/ = neural network building blocks (layers, autograd, attention)
//// - quant/ = quantization for memory efficiency (INT8, NF4, AWQ)
//// - optim/ = hardware-specific optimizations
////
//// Performance tip: for matrices > 100x100, make sure the NIF is compiled.
//// The difference is ~100-1000x. No, that's not a typo.
////
//// ```gleam
//// import viva_tensor as t
////
//// let a = t.zeros([2, 3])
//// let b = t.ones([2, 3])
//// let assert Ok(c) = t.add(a, b) // [2.0, 2.0, 2.0, 2.0, 2.0, 2.0]
//// ```
// Public API - re-exports from internal modules for convenience.
// Users can import viva_tensor and get everything they need.
import viva_tensor/tensor
import viva_tensor/tflops as tflops_mod
// --- Types ------------------------------------------------------------------
pub type Tensor =
tensor.Tensor
pub type TensorError =
tensor.TensorError
pub type Conv2dConfig =
tensor.Conv2dConfig
// --- Constructors -----------------------------------------------------------
/// All zeros. The tensor equivalent of a blank canvas.
pub fn zeros(shape: List(Int)) -> Tensor {
tensor.zeros(shape)
}
/// Create tensor of ones
pub fn ones(shape: List(Int)) -> Tensor {
tensor.ones(shape)
}
/// Create tensor filled with value
pub fn fill(shape: List(Int), value: Float) -> Tensor {
tensor.fill(shape, value)
}
/// Create tensor from list (1D)
pub fn from_list(data: List(Float)) -> Tensor {
tensor.from_list(data)
}
/// Create 2D tensor from list of lists
pub fn from_list2d(rows: List(List(Float))) -> Result(Tensor, TensorError) {
tensor.from_list2d(rows)
}
/// Create vector (1D tensor)
pub fn vector(data: List(Float)) -> Tensor {
tensor.vector(data)
}
/// Create matrix (2D tensor)
pub fn matrix(
rows: Int,
cols: Int,
data: List(Float),
) -> Result(Tensor, TensorError) {
tensor.matrix(rows, cols, data)
}
// --- Random -----------------------------------------------------------------
/// Random uniform [0, 1)
pub fn random_uniform(shape: List(Int)) -> Tensor {
tensor.random_uniform(shape)
}
/// Tensor with normal random values
pub fn random_normal(shape: List(Int), mean: Float, std: Float) -> Tensor {
tensor.random_normal(shape, mean, std)
}
/// Xavier initialization for neural network weights
pub fn xavier_init(fan_in: Int, fan_out: Int) -> Tensor {
tensor.xavier_init(fan_in, fan_out)
}
/// He initialization (for ReLU networks)
pub fn he_init(fan_in: Int, fan_out: Int) -> Tensor {
tensor.he_init(fan_in, fan_out)
}
// --- Math -------------------------------------------------------------------
/// Add element-wise
pub fn add(a: Tensor, b: Tensor) -> Result(Tensor, TensorError) {
tensor.add(a, b)
}
/// Element-wise subtraction
pub fn sub(a: Tensor, b: Tensor) -> Result(Tensor, TensorError) {
tensor.sub(a, b)
}
/// Element-wise multiplication
pub fn mul(a: Tensor, b: Tensor) -> Result(Tensor, TensorError) {
tensor.mul(a, b)
}
/// Element-wise division
pub fn div(a: Tensor, b: Tensor) -> Result(Tensor, TensorError) {
tensor.div(a, b)
}
/// Scale by constant
pub fn scale(t: Tensor, s: Float) -> Tensor {
tensor.scale(t, s)
}
/// Apply function to each element
pub fn map(t: Tensor, f: fn(Float) -> Float) -> Tensor {
tensor.map(t, f)
}
// --- Reductions -------------------------------------------------------------
/// Sum everything
pub fn sum(t: Tensor) -> Float {
tensor.sum(t)
}
/// Mean of all elements
pub fn mean(t: Tensor) -> Float {
tensor.mean(t)
}
/// Maximum value
pub fn max(t: Tensor) -> Float {
tensor.max(t)
}
/// Minimum value
pub fn min(t: Tensor) -> Float {
tensor.min(t)
}
/// Index of maximum value
pub fn argmax(t: Tensor) -> Int {
tensor.argmax(t)
}
/// Index of minimum value
pub fn argmin(t: Tensor) -> Int {
tensor.argmin(t)
}
/// Variance
pub fn variance(t: Tensor) -> Float {
tensor.variance(t)
}
/// Standard deviation
pub fn std(t: Tensor) -> Float {
tensor.std(t)
}
// --- Linear Algebra ---------------------------------------------------------
/// Dot product (vectors only)
pub fn dot(a: Tensor, b: Tensor) -> Result(Float, TensorError) {
tensor.dot(a, b)
}
/// Matrix-matrix multiplication
pub fn matmul(a: Tensor, b: Tensor) -> Result(Tensor, TensorError) {
tensor.matmul(a, b)
}
/// Matrix-vector multiplication
pub fn matmul_vec(mat: Tensor, vec: Tensor) -> Result(Tensor, TensorError) {
tensor.matmul_vec(mat, vec)
}
/// Matrix transpose
pub fn transpose(t: Tensor) -> Result(Tensor, TensorError) {
tensor.transpose(t)
}
/// Outer product
pub fn outer(a: Tensor, b: Tensor) -> Result(Tensor, TensorError) {
tensor.outer(a, b)
}
// --- Shape Ops --------------------------------------------------------------
/// Reshape (total size must match)
pub fn reshape(t: Tensor, new_shape: List(Int)) -> Result(Tensor, TensorError) {
tensor.reshape(t, new_shape)
}
/// Flatten to 1D
pub fn flatten(t: Tensor) -> Tensor {
tensor.flatten(t)
}
/// Remove dimensions of size 1
pub fn squeeze(t: Tensor) -> Tensor {
tensor.squeeze(t)
}
/// Add dimension of size 1
pub fn unsqueeze(t: Tensor, axis: Int) -> Tensor {
tensor.unsqueeze(t, axis)
}
// --- Accessors --------------------------------------------------------------
/// Shape as list of dimensions
pub fn shape(t: Tensor) -> List(Int) {
tensor.shape(t)
}
/// Get total size
pub fn size(t: Tensor) -> Int {
tensor.size(t)
}
/// Get rank (number of dimensions)
pub fn rank(t: Tensor) -> Int {
tensor.rank(t)
}
/// Convert to list
pub fn to_list(t: Tensor) -> List(Float) {
tensor.to_list(t)
}
// --- Utils ------------------------------------------------------------------
/// L2 norm (Euclidean length)
pub fn norm(t: Tensor) -> Float {
tensor.norm(t)
}
/// Normalize to unit length
pub fn normalize(t: Tensor) -> Tensor {
tensor.normalize(t)
}
/// Clamp values
pub fn clamp(t: Tensor, min_val: Float, max_val: Float) -> Tensor {
tensor.clamp(t, min_val, max_val)
}
// --- Broadcasting -----------------------------------------------------------
/// Can these shapes broadcast together?
pub fn can_broadcast(a: List(Int), b: List(Int)) -> Bool {
tensor.can_broadcast(a, b)
}
/// Add with broadcasting
pub fn add_broadcast(a: Tensor, b: Tensor) -> Result(Tensor, TensorError) {
tensor.add_broadcast(a, b)
}
/// Multiply with broadcasting
pub fn mul_broadcast(a: Tensor, b: Tensor) -> Result(Tensor, TensorError) {
tensor.mul_broadcast(a, b)
}
// --- Strided (Zero-copy) ----------------------------------------------------
/// Convert to strided representation for O(1) element access
pub fn to_strided(t: Tensor) -> Tensor {
tensor.to_strided(t)
}
/// Convert to contiguous tensor
pub fn to_contiguous(t: Tensor) -> Tensor {
tensor.to_contiguous(t)
}
/// Zero-copy transpose
pub fn transpose_strided(t: Tensor) -> Result(Tensor, TensorError) {
tensor.transpose_strided(t)
}
/// Check if contiguous
pub fn is_contiguous(t: Tensor) -> Bool {
tensor.is_contiguous(t)
}
// --- CNN Ops ----------------------------------------------------------------
// Convolution: the operation that made deep learning work for images.
// Yann LeCun et al. (1989) showed CNNs could recognize handwritten digits.
// 35 years later, we're using the same basic operation to generate cat pics.
/// Default conv2d config (3x3 kernel, stride 1, no padding)
pub fn conv2d_config() -> Conv2dConfig {
tensor.conv2d_config()
}
/// Conv2d config with "same" padding
pub fn conv2d_same(kernel_h: Int, kernel_w: Int) -> Conv2dConfig {
tensor.conv2d_same(kernel_h, kernel_w)
}
/// 2D Convolution
pub fn conv2d(
input: Tensor,
kernel: Tensor,
config: Conv2dConfig,
) -> Result(Tensor, TensorError) {
tensor.conv2d(input, kernel, config)
}
/// Pad 2D tensor with zeros
pub fn pad2d(t: Tensor, pad_h: Int, pad_w: Int) -> Result(Tensor, TensorError) {
tensor.pad2d(t, pad_h, pad_w)
}
/// Pad 4D tensor with zeros
pub fn pad4d(t: Tensor, pad_h: Int, pad_w: Int) -> Result(Tensor, TensorError) {
tensor.pad4d(t, pad_h, pad_w)
}
/// Max pooling 2D
pub fn max_pool2d(
input: Tensor,
pool_h: Int,
pool_w: Int,
stride_h: Int,
stride_w: Int,
) -> Result(Tensor, TensorError) {
tensor.max_pool2d(input, pool_h, pool_w, stride_h, stride_w)
}
/// Average pooling 2D
pub fn avg_pool2d(
input: Tensor,
pool_h: Int,
pool_w: Int,
stride_h: Int,
stride_w: Int,
) -> Result(Tensor, TensorError) {
tensor.avg_pool2d(input, pool_h, pool_w, stride_h, stride_w)
}
/// Global average pooling
pub fn global_avg_pool2d(input: Tensor) -> Result(Tensor, TensorError) {
tensor.global_avg_pool2d(input)
}
// --- TFLOPS Benchmarking ----------------------------------------------------
pub type TflopsBackend =
tflops_mod.Backend
pub type TflopsResult =
tflops_mod.TflopsResult
/// Measure TFLOPS for a single matmul operation
pub fn measure_tflops(
backend: TflopsBackend,
m: Int,
n: Int,
k: Int,
) -> TflopsResult {
tflops_mod.measure_matmul(backend, m, n, k)
}
/// Measure averaged TFLOPS (warmup + iterations)
pub fn measure_tflops_averaged(
backend: TflopsBackend,
m: Int,
n: Int,
k: Int,
iterations: Int,
) -> TflopsResult {
tflops_mod.measure_matmul_averaged(backend, m, n, k, iterations)
}
/// Detect available compute backends
pub fn detect_backends() -> List(TflopsBackend) {
tflops_mod.detect_backends()
}