Tensor Messages

Zero-copy tensor sharing between nodes for ML/AI workloads.

Tensor

A lightweight descriptor pointing to data in shared memory:

use horus::prelude::*; // Provides Tensor, TensorDtype, Device from horus_types

// Send tensor descriptor through Topic
let topic: Topic<Tensor> = Topic::new("camera.frames")?;

if let Some(tensor) = topic.recv() {
    println!("Shape: {:?}", tensor.shape());
    println!("Dtype: {:?}", tensor.dtype);
    println!("Device: {}", tensor.device());
}

All tensor types are available via use horus::prelude::*.

TensorDtype

DtypeSizeUse Case
F324ML training/inference
F648High-precision computation
F162Memory-efficient inference
BF162Training on modern GPUs
U81Images
U162Depth sensors (mm)
U324Large indices
U648Counters, timestamps
I81Quantized inference
I162Audio, sensor data
I324General integer
I648Large signed values
Bool1Masks

Helper methods:

let dtype = TensorDtype::F32;
assert_eq!(dtype.element_size(), 4);
assert!(dtype.is_float());
assert!(!dtype.is_signed_int());
println!("{}", dtype);  // "f32"

// DLPack interop
let dl = dtype.to_dlpack();
let back = TensorDtype::from_dlpack(dl.0, dl.1).unwrap();

// Parse from string
let parsed = TensorDtype::parse("float32").unwrap();

Device

The Device struct is a Pod-safe repr(C) descriptor that tags tensors with a target device. Device::cuda(N) creates a descriptor — actual GPU tensor pools are not yet implemented.

Device::cpu()      // CPU / shared memory
Device::cuda(0)    // CUDA device descriptor (metadata only)

// Parse from string
let cpu = Device::parse("cpu").unwrap();
let dev = Device::parse("cuda:0").unwrap();

// Check device type
assert!(Device::cpu().is_cpu());
assert!(Device::cuda(0).is_cuda());

Auto-Managed Tensor Pools

Topic<Tensor> automatically manages a shared-memory TensorPool per topic. Users call alloc_tensor(), send_handle(), and recv_handle() instead of managing pools manually:

use horus::prelude::*;

let topic: Topic<Tensor> = Topic::new("camera.rgb")?;

// Allocate a 1080p RGB image from the topic's auto-managed pool
let handle = topic.alloc_tensor(&[1080, 1920, 3], TensorDtype::U8, Device::cpu())?;

// Write pixel data
let pixels = handle.data_slice_mut()?;
// ... fill pixels ...

// Send — only the descriptor is transmitted, not the tensor data.
// The actual tensor data stays in shared memory — true zero-copy.
topic.send_handle(&handle);

On the receiver side:

let topic: Topic<Tensor> = Topic::new("camera.rgb")?;

if let Some(recv_handle) = topic.recv_handle() {
    let data = recv_handle.data_slice()?;  // Zero-copy access to shared memory
    println!("Shape: {:?}", recv_handle.shape());
    println!("Dtype: {:?}", recv_handle.dtype());
}
// TensorHandle is RAII — refcount decremented automatically on drop

The pool is created lazily on first use and shared across all Topic<Tensor> instances with the same name — even across processes. Pool IDs are derived deterministically from the topic name.

With Manual TensorPool

For advanced use cases, you can manage pools directly:

use horus::prelude::*;

let pool = TensorPool::new(1, TensorPoolConfig::default())?;
let handle = TensorHandle::alloc(
    Arc::new(pool),
    &[1080, 1920, 3],
    TensorDtype::U8,
    Device::cpu(),
)?;

// Write data
handle.data_slice_mut()?[0] = 255;

// Share via Topic
topic.send(*handle.tensor());

Domain Types

For common robotics data, use the high-level domain types Image, PointCloud, and DepthImage which provide rich APIs (pixel access, point extraction, depth queries) while using the same zero-copy shared memory transport internally. See Message Types for their full API.

Python Interop

import horus
import numpy as np

# TensorPool and TensorHandle are available from the native module
pool = horus.TensorPool(pool_id=1, size_mb=1024, max_slots=1024)
handle = pool.alloc(shape=(1080, 1920, 3), dtype="uint8")

See Also