Skip to main content

Tensor

Struct Tensor 

Source
pub struct Tensor<T>
where T: Num + Clone + Debug + Send + Sync,
{ /* private fields */ }
Expand description

Multi-backend tensor with optional image format metadata.

When format is Some, this tensor represents an image. Width, height, and channels are derived from shape + format. When format is None, this is a raw tensor (identical to the pre-refactoring behavior).

This is the static backend’s Tensor<T> – it owns its storage. See tensor_dyn/dynamic_tensor.rs for dynamic’s Tensor<T>, a typed lens with no storage of its own.

Implementations§

Source§

impl<T> Tensor<T>
where T: Num + Clone + Debug + Send + Sync,

Source

pub fn from_slice(values: &[T], shape: &[usize]) -> Result<Self>
where T: Copy,

Construct a tensor from a row-major element slice + shape. Allocates a new buffer (TensorMemory::Mem) and memcpys the contents; caller retains ownership of the input slice.

§Errors
Source

pub unsafe fn from_foreign( ptr: *mut T, shape: &[usize], owner: Option<ForeignOwner>, name: Option<&str>, ) -> Result<Self>

Wrap externally-owned memory as a tensor without copying. The tensor borrows [ptr, ptr + shape.product() * size_of::<T>()) as TensorMemory::Mem; owner, when Some, co-owns the source so it outlives the tensor (and all derived views/maps). See ForeignOwner.

The canonical use is CUDA zero-copy: allocate host-coherent memory (cudaHostAlloc), wrap the host pointer here, and bind the matching device pointer to the inference engine — reads and writes hit the same physical buffer with no host copy. The identical primitive backs the Python Tensor.from_numpy zero-copy borrow (owner = the NumPy object).

§Safety

ptr must be non-null, aligned to align_of::<T>(), and valid for shape.product() elements of T for as long as the returned tensor — and every view/map sharing its backing — is alive. Pass an owner that co-owns the source to uphold that contract.

§Errors

Error::InvalidSize if shape is empty.

Source

pub unsafe fn from_foreign_with_capacity( ptr: *mut T, shape: &[usize], capacity_bytes: usize, owner: Option<ForeignOwner>, name: Option<&str>, ) -> Result<Self>

Self::from_foreign, additionally recording that the allocation behind ptr holds at least capacity_bytes bytes – headroom beyond the tight shape.product() * size_of::<T>() footprint that a later configure_image / set_logical_shape can grow into without erroring.

This is the consumer half of the cross-package capsule protocol’s crate::protocol::TensorDesc::capacity: a producer’s pool tensor (allocated for its largest expected image, holding a smaller one today) reports its real allocation size so the imported alias inherits the same headroom instead of being clamped to today’s logical shape. capacity_bytes below the tight footprint is clamped up to it, never down – the shape-implied floor always holds.

§Safety

Same as Self::from_foreign, except ptr must be valid for capacity_bytes bytes (or the tight shape footprint, whichever is larger), not just the shape-implied footprint.

§Errors

Error::InvalidSize if shape is empty.

Source

pub fn pin_host<'a>(&self, access: CpuAccess) -> Result<HostPin<'a>>
where T: 'a,

Pin a stable host address for this tensor’s data.

The returned HostPin carries no borrow of the tensor, so a caller can pin once at init and still pass &mut self to ImageProcessor::convert every frame. That borrow conflict is the blocker reported in #134.

The address stays valid until the pin is dropped, and survives re-layout: configure_image and set_logical_shape only rewrite the logical shape after a capacity check, and every backend’s pin owns its own mapping, so nothing can move underneath it.

A pin is therefore a snapshot, not a tracker: HostPin::len reports the extent at pin time and does not follow a later re-layout. That is the safe direction — the window always stays inside the allocation — but it means a caller who re-layouts should re-pin if it wants the new extent.

The address is not a coherency guarantee: on non-coherent backends bracket CPU access with sync_for_cpu and sync_for_device, which are no-ops where the backend does not require them.

§Cost

On Mem this is an Arc refcount bump — the backing allocation is never resized, so its base pointer is already stable. Other backends establish a mapping once and reuse it.

§Errors
  • Error::InvalidArgument when access is CpuAccess::None: a pinned mapping is CPU access by definition, so this is an error rather than a warn-and-stage.
  • Error::NotImplemented for the two backends that cannot pin, rather than merely not having been wired yet: a PBO (glMapBufferRange establishes the address and the visibility together, and there is no GL_MAP_PERSISTENT_BIT in GLES) and an AHardwareBuffer (AHardwareBuffer_lock is what yields the address at all, so there is no address outside the lock). Both name map()/map_with() as the alternative. Mem, Shm, DMA-BUF, IOSurface and the Windows D3D11 texture all pin — the texture through a persistent host buffer the storage owns, which sync_for_cpu and sync_for_device move bytes between and the texture, rather than through the staging mapping (a pin outlives every map guard, so holding the staging texture mapped would freeze it).
Source

pub fn sync_for_cpu(&self, access: CpuAccess) -> Result<()>

Make CPU reads/writes of this tensor coherent — the CPU is about to access the buffer.

Corresponds to DMA_BUF_SYNC_START, and the name matches the Linux kernel DMA API for the same reason: for_cpu says who is about to touch the memory, which is what the caller actually knows.

Pair with sync_for_device.

§Which backends owe maintenance
backendbehaviour
Mem, Shmno-op — plain CPU memory, coherent by construction
Dma (Linux DMA-BUF)one DMA_BUF_IOCTL_SYNC, direction-tagged
Dma (macOS/iOS IOSurface)IOSurfaceLock / IOSurfaceUnlock, options matched to access
Dma (Android AHardwareBuffer)Error::NotImplemented — no sync independent of the lock
Dma (Windows D3D11 texture)refreshes the pinned host buffer from the texture through the staging copy, or uploads it back; a no-op until something pins
PboError::NotImplemented — no coherency window independent of the map

Only the first row is a no-op, so “bracket unconditionally and pay nothing” holds for Mem/Shm alone. The two that report NotImplemented do so because their platform fuses addressing and coherency into one call — AHardwareBuffer_lock and glMapBufferRange each perform the maintenance as they hand out the address, so a separate bracket would have to nest inside a map guard that already owns the pair. Both name map()/map_with() as the alternative. They previously fell through a wildcard and answered Ok(()), which is worse than an error because the symptom is stale-but-plausible pixels rather than a failure. The match is exhaustive so a new backend cannot inherit that silence — it must state its coherency story to compile.

§Cost

Mem/Shm: nothing. Dma on Linux: one ioctl. IOSurface: one lock/unlock pair. Windows D3D11 texture: one CopyResource plus a staging map per direction, and nothing at all on a tensor no one has pinned.

§Errors

Error::InvalidArgument when access is CpuAccess::None, which describes no access to make coherent.

Source

pub fn sync_for_device(&self, access: CpuAccess) -> Result<()>

Release the buffer back to the device — the CPU is done accessing it.

Corresponds to DMA_BUF_SYNC_END. See sync_for_cpu for the pairing and cost.

Source

pub fn from_arrayview3(view: ArrayView3<'_, T>) -> Result<Self>
where T: Copy,

Construct a tensor from a 3-D ndarray view. Respects strides — one copy in all cases; contiguous views take a memcpy fast path.

Only available when the ndarray feature is enabled.

Source

pub fn new( shape: &[usize], memory: Option<TensorMemory>, name: Option<&str>, ) -> Result<Self>

Create a new tensor with the given shape, memory type, and optional name. If no name is given, a random name will be generated. If no memory type is given, the best available memory type will be chosen based on the platform and environment variables.

On Linux platforms, the order of preference is: Dma -> Shm -> Mem. On other Unix platforms (macOS), the order is: Shm -> Mem. On non-Unix platforms, only Mem is available.

§Environment Variables
  • EDGEFIRST_TENSOR_FORCE_MEM: If set to a non-zero and non-false value, forces the use of regular system memory allocation (TensorMemory::Mem) regardless of platform capabilities.
§Example
use edgefirst_tensor::{Error, Tensor, TensorMemory, TensorTrait};
let tensor = Tensor::<f32>::new(&[2, 3, 4], Some(TensorMemory::Mem), Some("test_tensor"))?;
assert_eq!(tensor.memory(), TensorMemory::Mem);
assert_eq!(tensor.name(), "test_tensor");
Source

pub fn image_desc(desc: &ImageDesc) -> Result<Self>
where T: 'static,

Create an image tensor with the given format. Allocate an image tensor from a declarative request — the full-featured constructor behind Self::image and friends.

Adds the Compression request to the classic parameters:

The recorded outcome is readable via Tensor::compression.

Source

pub fn image( width: usize, height: usize, format: PixelFormat, memory: Option<TensorMemory>, access: CpuAccess, ) -> Result<Self>
where T: 'static,

Allocate an image tensor of width × height in format.

This is the common-case image constructor. The shape follows from the format’s layout — packed [H, W, C], planar [C, H, W], semi-planar [H + chroma_rows, W] — and the format is recorded on the tensor, so the image crate and the codec can work from it without out-of-band parameters.

memory requests a backend, or None to auto-select. Auto-select for images is DMA → Mem: unlike Tensor::new it does not try SHM, which offers an image nothing that Mem doesn’t (it is not GPU-importable, and Mem always succeeds). Ask for TensorMemory::Shm explicitly if you need it.

access declares what the CPU intends to do with the buffer and is required; see CpuAccess. Hardware access — GPU, NPU, ISP, codec — is always implied and never declared. Prefer the narrowest declaration that fits: None keeps the allocation eligible for Android vendor tile compression, and anything else pins the layout linear.

DMA-backed images always get a 64-byte-aligned row stride, in every layout, because Mali and Vivante reject an EGLImage import at an unaligned pitch. Read the real pitch back with Tensor::effective_row_stride rather than assuming width × bpp.

For a compression request or any other less common knob, build an ImageDesc and call Tensor::image_desc.

§Errors
  • Error::InvalidArgument if width × height is not a valid size for format (for example odd dimensions where the format’s chroma subsampling forbids them), or, on macOS with an explicit TensorMemory::DmaBuf, if the format cannot be expressed as an image-formatted IOSurface at this width. The message names the aligned width to use.
  • Whatever the chosen backend returns if the allocation itself fails — for an explicit memory request there is no fallback.
§Example
use edgefirst_tensor::{CpuAccess, PixelFormat, Tensor, TensorMemory};

// A camera frame the GPU converts and the CPU never touches.
let frame = Tensor::<u8>::image(1920, 1080, PixelFormat::Nv12,
                                Some(TensorMemory::Mem), CpuAccess::None)?;
assert_eq!(frame.format(), Some(PixelFormat::Nv12));
Source

pub fn image_with_stride( width: usize, height: usize, format: PixelFormat, row_stride_bytes: usize, memory: Option<TensorMemory>, access: CpuAccess, ) -> Result<Self>

Create a DMA-backed image tensor with an explicit row stride that may exceed the natural width * channels * sizeof(T) pitch.

Used for image tensors that need GPU pitch alignment padding: the underlying DMA-BUF is sized to row_stride * height bytes, but the tensor’s logical shape stays at [height, width, channels]. width() / height() / shape() continue to report the user-requested values; the padding is visible only via row_stride() / effective_row_stride() and is automatically propagated to the GL backend’s EGLImage import so Mali Valhall accepts the buffer.

§Supported formats

Currently only packed pixel layouts (RGBA8, BGRA8, RGB888, Grey, etc.) are supported — the formats the GL backend uses as render destinations. Semi-planar formats (NV12, NV16) come from external allocators (camera capture, video decoders) and are imported via TensorDyn::from_fd + set_row_stride, which already supports padded strides.

§Supported memory

Currently only TensorMemory::DmaBuf is supported. PBO and Mem storage don’t go through EGLImage import so they don’t need pitch alignment; if you pass any other memory type this returns NotImplemented. None (auto-select) is treated as Dma.

§Errors
  • InvalidArgument if row_stride_bytes < width * channels * sizeof(T) (the requested stride would not fit a single row)
  • NotImplemented for non-packed formats or non-DMA memory
  • IoError if the DMA-heap allocation fails (propagated from DmaTensor::new_with_byte_size)
Source

pub fn set_format(&mut self, format: PixelFormat) -> Result<()>

Attach format metadata to an existing tensor.

§Arguments
  • format - The pixel format to attach
§Returns

Ok(()) on success, with the format stored as metadata on the tensor.

§Errors

Returns Error::InvalidShape if the tensor shape is incompatible with the format’s layout (packed expects [H, W, C], planar expects [C, H, W], semi-planar expects [H*k, W] with format-specific height constraints).

Source

pub fn configure_image( &mut self, width: usize, height: usize, format: PixelFormat, ) -> Result<()>

Set this tensor’s logical dimensions and pixel format to a decoded image, reusing the existing allocation. The shape is derived from the format layout; fails with Error::InsufficientCapacity if the allocation cannot hold width×height in format, or Error::InvalidArgument if the dimensions are invalid for the format.

For NV12/NV16/NV24 the buffer width is rounded up to even (a chroma-plane interleaving requirement); the true odd width is reported by the decoder in ImageInfo and trimmed by a convert() crop. See PixelFormat::allocation_shape.

When the backing has a fixed physical row pitch (an IOSurface’s 64-aligned bytesPerRow) that exceeds the new format’s natural row stride — i.e. a reused max-sized pool tensor reconfigured to a smaller image — the physical pitch is preserved as the tensor’s row_stride. This keeps the physical grid (allocation stride/surface) fixed while the logical ROI (this image’s W×H) changes, so the decode writes rows at the surface’s real stride and the GPU samples them at the same stride. Exact-sized buffers (pitch == natural) stay tightly packed unchanged.

Source

pub fn image_with_capacity( width: usize, height: usize, format: PixelFormat, memory: Option<TensorMemory>, access: CpuAccess, ) -> Result<Self>
where T: 'static,

Allocate an image tensor sized to hold up to width×height in format, reusable for any smaller image via configure_image.

Source

pub fn format(&self) -> Option<PixelFormat>

Pixel format (None if not an image).

Source

pub fn width(&self) -> Option<usize>

Image width (None if not an image).

Source

pub fn height(&self) -> Option<usize>

Image height (None if not an image).

For semi-planar formats the combined-plane shape row count is divided by the format’s luma-to-total ratio to recover logical height. This returns the exact logical height (including odd heights) only because the logical dimensions are tracked separately from the physical shape — configure_image stores the actual (width, height) in the format’s allocation_shape, which round-trips losslessly via these accessors.

Source

pub fn from_planes( luma: Tensor<T>, chroma: Tensor<T>, format: PixelFormat, ) -> Result<Self>

Create from separate Y and UV planes (multiplane NV12/NV16).

Source

pub fn is_multiplane(&self) -> bool

Whether this tensor uses separate plane allocations.

Source

pub fn chroma(&self) -> Option<&Tensor<T>>

Access the chroma plane for multiplane semi-planar images.

Source

pub fn chroma_mut(&mut self) -> Option<&mut Tensor<T>>

Mutable access to the chroma plane for multiplane semi-planar images.

Source

pub fn row_stride(&self) -> Option<usize>

Row stride in bytes (None = tightly packed).

Source

pub fn effective_row_stride(&self) -> Option<usize>

Effective row stride in bytes: the stored stride if set, otherwise the minimum stride computed from the format, width, and element size. Returns None only when no format is set and no explicit stride was stored via set_row_stride.

GREY note: effective_row_stride() for a GREY tensor returns the tight width bytes (no padding), which is what normalize_to_numpy and the CPU convert path expect. The codec’s internal native_row_stride (64-byte-aligned) is used only during decoding and is not propagated to the tensor’s stored stride, so callers reading via effective_row_stride() always see the tight value for GREY.

Source

pub fn copy_to_flat(&self, dst: &mut [u8]) -> Result<()>

Copy the tensor’s logical bytes into dst, compacting away any recorded row-stride padding.

The flatness helper for NPU handoff: consumers that need a FLAT layout (e.g. [1, C, H, W] for NNAPI/LiteRT when the runtime cannot take a padded pitch) call this when row_stride is Some — on a tight tensor it degenerates to one memcpy, so it is safe to call unconditionally. dst.len() must equal the tight byte footprint (shape product × element size). Zero-copy consumers should prefer the buffer handle + Tensor::effective_row_stride (Self::effective_row_stride) and skip this copy entirely.

Source

pub fn set_row_stride(&mut self, stride: usize) -> Result<()>

Set the row stride in bytes for externally allocated buffers with row padding (e.g. V4L2 or GStreamer allocators).

The stride is propagated to the EGL DMA-BUF import attributes so the GPU interprets the padded buffer layout correctly. Must be called after set_format and before the tensor is first passed to ImageProcessor::convert. The stored stride is cleared automatically if the pixel format is later changed.

No stride-vs-buffer-size validation is performed because the backing allocation size is not reliably known: external DMA-BUFs may be over-allocated by the allocator, and internal tensors store a logical (unpadded) shape. An incorrect stride will be caught by the EGL driver at import time.

§Arguments
  • stride - Row stride in bytes. Must be >= the minimum stride for the format (width * channels * sizeof(T) for packed, width * sizeof(T) for planar/semi-planar).
§Errors
  • InvalidArgument if no pixel format is set on this tensor
  • InvalidArgument if stride is less than the minimum for the format and width
Source

pub fn set_row_stride_unchecked(&mut self, stride: usize)

Set the row stride without format validation.

Use this for raw sub-tensors (e.g. chroma planes) that don’t carry format metadata. The caller is responsible for ensuring the stride is valid.

Source

pub fn with_row_stride(self, stride: usize) -> Result<Self>

Builder-style variant of set_row_stride, consuming and returning self.

§Errors

Same conditions as set_row_stride.

Source

pub fn plane_offset(&self) -> Option<usize>

Byte offset within the DMA-BUF where image data starts (None = 0).

Source

pub fn view_origin(&self) -> Option<ViewOrigin>

The parent-image snapshot if this tensor is a view/ batch sub-region; None for a whole tensor. The GL backend keys its import on the parent geometry and renders this view as a glViewport/glScissor ROI at (x, y, width, height). See ViewOrigin.

Source

pub fn set_plane_offset(&mut self, offset: usize)

Set the byte offset within the DMA-BUF where image data starts.

Propagated to EGL_DMA_BUF_PLANE0_OFFSET_EXT on GPU import. Unlike set_row_stride, no format is required since the offset is format-independent.

Source

pub fn cpu_access(&self) -> CpuAccess

Colorimetry metadata (None = undefined; never auto-filled). The CPU access declared for this tensor at allocation (see CpuAccess). Views share their parent’s declaration.

Source

pub fn compression(&self) -> Option<CompressionScheme>

The vendor tile-compression scheme recorded at allocation, or None for a linear layout. Some means the pixels live in a proprietary tile order: the row-stride accessors describe no meaningful linear layout and CPU maps are best-effort (see Compression). Only Android hardware-only allocations that requested compression record a scheme.

Source

pub fn colorimetry(&self) -> Option<Colorimetry>

Source

pub fn set_colorimetry(&mut self, c: Option<Colorimetry>)

Attach/clear colorimetry metadata.

Source

pub fn with_colorimetry(self, c: Colorimetry) -> Self

Builder-style colorimetry attach.

Source

pub fn batch(&self, n: usize) -> Result<Tensor<T>>

Borrow batch element n of a batched tensor as a zero-copy view.

A batched tensor prepends N as the leading dimension over the per-element image layout ([N, H, W, C] packed or [N, C, H, W] planar) — N is over the whole per-element block regardless of HWC/CHW. batch(n) returns element n: the contiguous per-element region at byte offset n * element_size, sharing the parent’s BufferIdentity and inheriting its format / row stride / colorimetry. batch(0) on a tensor with N == 1 is equivalent to the whole tensor.

§Errors
Source

pub fn view(&self, region: Region) -> Result<Tensor<T>>

Borrow a rectangular spatial sub-region of an image tensor as a zero-copy view — the destination/source crop primitive.

region is in pixels of the image’s leading frame. The returned view shares the parent’s BufferIdentity and addresses the sub-rectangle by offset + the parent’s row pitch (so each row lands at the correct columns). convert(src, &mut dst.view(rect), …) renders into that sub-rectangle of dst; a letterbox fit then clears the view and renders the aspect-preserved content into its inner region. view/batch/the whole tensor are the one coherent destination model — there is no separate dst_rect.

§Known limitation: bottom-right multi-row views

A multi-row view is mapped as parent_pitch × rows bytes from its own offset, so the window it asks for overruns the allocation by region.x * bpp whenever the view is both offset horizontally and reaches the parent’s last row. Such a view constructs fine but fails at map time with Error::InsufficientCapacity — loudly, never as an out-of-bounds access. For a tiled destination this is the bottom row of tiles at every column but the first. The single-row case is already special-cased below (it keeps its own tight stride); the general fix is to clamp the last row’s exposure to the view’s own row bytes, which is not yet implemented.

§Errors
Source

pub fn pbo_id(&self) -> Option<u32>

The GL buffer ID backing this tensor (for GL backends / the cross-package protocol’s handle). None when the tensor is not PBO-backed.

Source

pub fn pbo_is_mapped(&self) -> Option<bool>

Whether this PBO currently holds – or is establishing – a CPU mapping. None when the tensor is not PBO-backed.

The question a GL caller is really asking is “is this buffer free for GL operations?”, so a map or unmap still in flight answers Some(true): the GL side must not touch the buffer until the transition finishes. See PboTensor::is_mapped.

Source

pub fn pbo_vtable_ptr(&self) -> Option<*const c_void>

The C-ABI PboOpsVtable address backing this tensor (for the cross-package protocol’s ptr under kind::PBO; see crate::protocol::TensorDesc::ptr’s doc comment). None when the tensor is not PBO-backed.

Source

pub fn pbo_keepalive(&self) -> Option<Arc<dyn Send + Sync>>

A type-erased keepalive that must stay alive for at least as long as Self::pbo_vtable_ptr’s address is used – see PboTensor::pbo_keepalive’s own doc comment. None when the tensor is not PBO-backed.

Source

pub fn as_dma(&self) -> Option<&DmaTensor<T>>

Downcast to DMA tensor reference (for EGL import, G2D).

Source

pub fn dmabuf(&self) -> Result<BorrowedFd<'_>>

Borrow the DMA-BUF file descriptor backing this tensor.

§Returns

A borrowed reference to the DMA-BUF file descriptor, tied to self’s lifetime.

§Errors

Returns Error::NotImplemented if the tensor is not DMA-backed.

Source

pub fn from_pbo(pbo: PboTensor<T>) -> Result<Self>

Construct a Tensor from a PBO tensor (for GL backends that allocate PBOs).

Infallible in this backend (Ok always) – kept as Result to match dynamic’s own Tensor::from_pbo (dynamic_tensor.rs), which genuinely can fail: it must allocate a real ef_tensor_* handle alongside pbo to carry shape/dtype/format metadata (this backend has nowhere else to put it), an allocation this backend never needed since it just wraps pbo into TensorStorage::Pbo directly. One shared, fallible signature lets edgefirst-image’s own call sites (gl/threaded.rs) stay backend-agnostic.

Source

pub fn cuda(&self) -> Option<&CudaHandle>

The CUDA registration for this tensor, if any (set at creation on CUDA devices).

Source

pub fn set_cuda_handle(&mut self, h: CudaHandle)

Attach a CUDA handle (called by ImageProcessor::create_image after registering a PBO).

Source

pub fn cuda_map(&self) -> Option<CudaMap<'_>>

Fast-fail CUDA map: None (no GL routing) when no handle; else map (PBO routes to the GL worker).

Returns a scoped CudaMap guard holding the raw CUDA device pointer for the duration of the mapping. For GL-buffer-backed tensors the unmap is deferred until the guard drops, freeing the PBO for the next convert() call. When no CUDA handle is attached (the common case for plain Mem/DMA tensors without CUDA registration), returns None immediately — no GL routing, no allocation.

§Example — zero-copy CUDA input with host fallback
use edgefirst_tensor::{Tensor, TensorMemory, TensorTrait};
// Try the zero-copy CUDA device pointer first.
if let Some(cuda) = t.cuda_map() {
    feed_tensorrt(cuda.device_ptr(), cuda.len());
    // `cuda` (a CudaMap guard) unmaps when it goes out of scope, freeing
    // the GPU buffer for the next convert().
} else {
    // Fall back to the host mapping when no CUDA handle is attached.
    let _host = t.map().expect("host map fallback must succeed");
    // `_host` is a TensorMap<f32> that derefs to &[f32].
}
§D3D11 texture tensors: the mapping is tight rows

On Windows the mapping is a linear copy of the texture laid out at width * bytes_per_texel, and len() is that times the row count – not row_stride, which reports the D3D11 staging pitch a CPU map sees and is the larger number on a padded backing. A consumer that strides the device pointer by row_stride() reads past the end of the mapping.

Semi-planar formats (NV12, NV16, NV24) are the exception: their texture is allocated as wide as its own row pitch, so the tight row is the pitch and the mapping equals row_stride() x combined_height bytes. Striding the device pointer by row_stride() is exactly right there.

Source

pub fn cuda_map_mut(&self) -> Option<CudaMap<'_>>

Writable counterpart of cuda_map. See CudaHandle::map_mut for which backings distinguish the two.

The D3D11 arm is the one that does: the guard’s release copies the buffer back into the texture. Its layout is cuda_map’s – tight rows of width * bytes_per_texel, not row_stride() – and writing it at the wrong pitch scrambles the image rather than failing.

Source

pub fn try_init_dma_cuda(&mut self)

Attempt to attach a CUDA ExternalMemory handle for DMA-backed tensors.

On a CUDA-capable host, imports the DMA-BUF fd via cudaImportExternalMemory(OpaqueFd) and maps it to a device pointer. Sets self.cuda to a persistent ExternalMem handle on success. No-op if CUDA is unavailable, the tensor is not DMA-backed, or a handle is already set. Import failure is silently ignored — the tensor remains usable without a CUDA handle.

§RUNTIME-UNVALIDATED

No test platform has both /dev/dma_heap and a CUDA device. ABI is layout-asserted vs. CUDA 12.6 driver_types.h; the mechanism is proven by gpu-probe O5 on Orin. Best-effort: tensor creation never fails here.

Source§

impl<T> Tensor<T>
where T: Num + Clone + Debug + Send + Sync,

The recorded GPU write value, on every platform: the descriptor path and the Python bindings forward it without a cfg of their own, and only Windows can record one.

Source

pub fn gpu_write_value(&self) -> u64

The fence value of the newest GPU write recorded on this tensor, or 0 when there is none: the value of gpu_completion without the duplicated fence handle, for a consumer that already holds the process fence. 0 for a tensor that is not a D3D11 texture, and on every platform other than Windows.

Source§

impl<T> Tensor<T>
where T: IntegerType + Num + Clone + Debug + Send + Sync,

Source

pub fn quantization(&self) -> Option<&Quantization>

Quantization metadata for this tensor, if set.

Source

pub fn set_quantization(&mut self, q: Quantization) -> Result<()>

Attach quantization metadata to this tensor. Validates against the tensor’s shape — returns Error::QuantizationInvalid on any inconsistency (mismatched scale/zp lengths, out-of-range axis, etc.).

Source

pub fn with_quantization(self, q: Quantization) -> Result<Self>

Builder-style variant of Self::set_quantization. Consumes self and returns Result<Self> — on success yields the tensor with the attached quantization; on validation failure returns Error::QuantizationInvalid and drops self (the tensor is not returned in the error arm).

Source

pub fn clear_quantization(&mut self)

Clear any quantization metadata on this tensor.

Trait Implementations§

Source§

impl<T> Debug for Tensor<T>
where T: Num + Clone + Debug + Send + Sync + Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<Tensor<f16>> for TensorDyn

Source§

fn from(t: Tensor<f16>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<f32>> for TensorDyn

Source§

fn from(t: Tensor<f32>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<f64>> for TensorDyn

Source§

fn from(t: Tensor<f64>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<i8>> for TensorDyn

Source§

fn from(t: Tensor<i8>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<i16>> for TensorDyn

Source§

fn from(t: Tensor<i16>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<i32>> for TensorDyn

Source§

fn from(t: Tensor<i32>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<i64>> for TensorDyn

Source§

fn from(t: Tensor<i64>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<u8>> for TensorDyn

Source§

fn from(t: Tensor<u8>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<u16>> for TensorDyn

Source§

fn from(t: Tensor<u16>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<u32>> for TensorDyn

Source§

fn from(t: Tensor<u32>) -> Self

Converts to this type from the input type.
Source§

impl From<Tensor<u64>> for TensorDyn

Source§

fn from(t: Tensor<u64>) -> Self

Converts to this type from the input type.
Source§

impl<T> TensorTrait<T> for Tensor<T>
where T: Num + Clone + Debug + Send + Sync,

Available on crate feature static only.
Source§

fn capacity_bytes(&self) -> usize

Bytes available in the underlying allocation, from this tensor’s window start – may exceed TensorTrait::size when the backing carries spare room beyond the current logical shape (a pitch-aligned image, or a pool tensor sized for a larger image than it holds today). See crate::protocol::TensorDesc::capacity, the cross-package descriptor field this backs.

Source§

fn set_logical_shape(&mut self, shape: &[usize]) -> Result<()>

Set the logical shape to any shape whose bytes fit the allocation, without reshape’s equal-count constraint.

Overridden rather than inherited, which it was not for most of this crate’s life. The trait’s default body is self.reshape(shape), so a caller reaching this through Tensor<T> silently got the strict rule under a name that promises the opposite – on both backends, which is why G13 cannot see it.

The capacity-aware implementations were never dead: every storage type has one, and Tensor::configure_image reaches them by calling self.storage.set_logical_shape(..) directly. That is the pool-reuse path – a decode into an oversized reusable buffer reconfiguring it to a smaller image without reallocating – and it worked precisely because it bypassed this method. This forwards the same way, so the two agree.

A narrower image inside a pitched backing (an IOSurface, an AHardwareBuffer, a D3D11 texture) still advances rows by the backing’s pitch, so that pitch is recorded as the row stride whenever it exceeds the new shape’s natural stride – the same adoption configure_image makes. Without it a texture narrowed from 64 to 16 texels wide reports a 64-byte stride over rows that are 256 bytes apart. A single-row shape records its own tight stride instead, by view’s rule: a strided map() spans stride * rows, and one pitched row runs past the backing when the window sits in the last row.

Source§

fn new(shape: &[usize], name: Option<&str>) -> Result<Self>
where Self: Sized,

Create a new tensor with the given shape and optional name. If no name is given, a random name will be generated.
Source§

fn from_fd(fd: OwnedFd, shape: &[usize], name: Option<&str>) -> Result<Self>
where Self: Sized,

Import an existing buffer as a tensor, taking ownership of its file descriptor. The buffer is adopted in place — no bytes are copied. Read more
Source§

fn clone_fd(&self) -> Result<OwnedFd>

Clone the file descriptor associated with this tensor.
Source§

fn memory(&self) -> TensorMemory

Get the memory type of this tensor.
Source§

fn name(&self) -> String

Get the name of this tensor.
Source§

fn shape(&self) -> &[usize]

Get the shape of this tensor.
Source§

fn reshape(&mut self, shape: &[usize]) -> Result<()>

Reshape this tensor to the given shape. The total number of elements must remain the same.
Source§

fn map_with<'a>(&self, access: CpuAccess) -> Result<HostView<'a, T>>
where T: 'a,

Map the tensor into memory with the given access direction and return a TensorMap for accessing the data. Read more
Source§

fn try_map_with<'a>(&self, access: CpuAccess) -> Result<HostView<'a, T>>
where T: 'a,

Non-blocking map_with: returns Err(Error::IoError(WouldBlock)) while a GPU copy the map depends on is still in flight, and makes progress on a retry. Read more
Source§

fn buffer_identity(&self) -> &BufferIdentity

Get the buffer identity for cache keying and liveness tracking.
Source§

fn len(&self) -> usize

Get the number of elements in this tensor.
Source§

fn is_empty(&self) -> bool

Check if the tensor is empty.
Source§

fn size(&self) -> usize

Get the size in bytes of this tensor.
Source§

fn map<'a>(&self) -> Result<HostView<'a, T>>
where T: 'a,

Map the tensor read-write (equivalent to map_with(CpuAccess::ReadWrite) — the historical map() behavior).
Source§

fn map_read<'a>(&self) -> Result<HostView<'a, T>>
where T: 'a,

Map the tensor for CPU reading only. The returned map rejects as_mut_slice; on macOS this takes the read-only IOSurface lock (skips the unlock flush), on Linux the dma-buf read-direction sync.
Source§

fn map_write<'a>(&self) -> Result<HostView<'a, T>>
where T: 'a,

Map the tensor for CPU writing (fill-only: reading through a write map may see write-combined memory — do not read the slice).
Source§

fn map_mut<'a>(&self) -> Result<HostView<'a, T>>
where T: 'a,

Map the tensor read-write (alias of map with the intent spelled out).
Source§

fn try_map<'a>(&self) -> Result<HostView<'a, T>>
where T: 'a,

Non-blocking map: read-write, and WouldBlock rather than a stall. See try_map_with.
Source§

fn view(&self, offset_bytes: usize, shape: &[usize]) -> Result<Self>
where Self: Sized,

Create a zero-copy sub-region view of this backing that shares the underlying allocation and BufferIdentity. Read more

Auto Trait Implementations§

§

impl<T> !RefUnwindSafe for Tensor<T>

§

impl<T> !UnwindSafe for Tensor<T>

§

impl<T> Freeze for Tensor<T>
where TensorStorage<T>: Freeze, Option<Box<Tensor<T>>>: Freeze,

§

impl<T> Send for Tensor<T>
where TensorStorage<T>: Send, Option<Box<Tensor<T>>>: Send,

§

impl<T> Sync for Tensor<T>
where TensorStorage<T>: Sync, Option<Box<Tensor<T>>>: Sync,

§

impl<T> Unpin for Tensor<T>
where TensorStorage<T>: Unpin, Option<Box<Tensor<T>>>: Unpin,

§

impl<T> UnsafeUnpin for Tensor<T>
where TensorStorage<T>: UnsafeUnpin, Option<Box<Tensor<T>>>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more