Tensor Messages
Zero-copy tensor sharing between nodes for ML/AI workloads.
Tensor
A 168-byte Pod descriptor pointing to data in shared memory:
use horus::types::Tensor; // Tensor descriptor (not in the prelude)
use horus::prelude::*; // Topic, TensorDtype, Device
// 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());
}
TensorDtype and Device are in use horus::prelude::*. Tensor lives in horus::types, and TensorPool / TensorPoolConfig / TensorHandle in horus::memory — import those explicitly.
TensorDtype
| Dtype | Size | Use Case |
|---|---|---|
| F32 | 4 | ML training/inference |
| F64 | 8 | High-precision computation |
| F16 | 2 | Memory-efficient inference |
| BF16 | 2 | Training on modern GPUs |
| U8 | 1 | Images |
| U16 | 2 | Depth sensors (mm) |
| U32 | 4 | Large indices |
| U64 | 8 | Counters, timestamps |
| I8 | 1 | Quantized inference |
| I16 | 2 | Audio, sensor data |
| I32 | 4 | General integer |
| I64 | 8 | Large signed values |
| Bool | 1 | Masks |
Helper methods:
let dtype = TensorDtype::F32;
assert_eq!(dtype.element_size(), 4);
// There is no is_float() / is_signed_int() helper — match on the variants
assert!(!matches!(dtype, TensorDtype::I8 | TensorDtype::I16 | TensorDtype::I32 | TensorDtype::I64));
println!("{}", dtype); // "float32"
// DLPack interop — to_dlpack() returns (code, bits, lanes)
let (code, bits, lanes) = dtype.to_dlpack();
let back = TensorDtype::from_dlpack(code, bits, lanes).unwrap();
// Parse from string
let parsed = TensorDtype::parse("float32").unwrap();
Device
The Device struct replaces the old TensorDevice enum. It's a Pod-safe repr(C) struct supporting unlimited GPU indices:
Device::cpu() // CPU / shared memory
Device::cuda(0) // GPU 0
Device::cuda(1) // GPU 1
Device::cuda(7) // GPU 7 — no limit!
// Parse from string
let dev = Device::parse("cuda:2").unwrap();
let cpu = Device::parse("cpu").unwrap();
// Check device type
assert!(Device::cpu().is_cpu());
assert!(Device::cuda(0).is_cuda());
println!("{}", Device::cuda(1)); // "cuda:1"
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::types::Tensor;
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 168-byte descriptor flows through the ring buffer.
// 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::types::Tensor;
use horus::memory::{TensorHandle, TensorPool, TensorPoolConfig};
use horus::prelude::*;
use std::sync::Arc;
// Use a separate topic: a manual pool has its own pool_id, so receivers must
// pair `topic.recv()` with this same pool. `recv_handle()` only understands
// the auto-managed pool derived from the topic name.
let topic: Topic<Tensor> = Topic::new("camera.rgb.manual")?;
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 Tensor (the handle class) 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
- TensorPool API — Pool management and configuration
- Message Types — All HORUS message types