This document is relevant for: Trn2, Trn3
nki.language.NkiTensor#
- class nki.language.NkiTensor(shape: tuple[int, ...], dtype: str, storage: Any, buffer: nki.language.MemoryRegion | str = Ellipsis, name: str = '', address=None)[source]#
NKI tensor with shape-based view operations.
NkiTensoris the core tensor type in NKI. It represents a multi-dimensional array allocated on a specific memory buffer (SBUF, PSUM, or HBM) with a dtype and shape. All view operations (slice,permute,reshape, etc.) return newNkiTensorobjects that share the same underlying storage — no data is copied. Views are consumed by NKI ISA instructions (e.g.,nisa.tensor_copy,nisa.dma_copy).Tensors are created via
nl.ndarray():sb = nl.ndarray((128, 64), dtype=nl.float32, buffer=nl.sbuf) hbm = nl.ndarray((4, 128, 64), dtype=nl.float32, buffer=nl.shared_hbm)
Partition dimension.
On-chip tensors (SBUF, PSUM) have a partition dimension at dim 0 that maps to the hardware’s parallel partitions. Most view operations cannot modify this dimension — see individual method docs for constraints.
Methods
Low-level access pattern override (escape hatch).
Expand a size-1 dimension to
sizeby repeating elements.Insert a new dimension of size 1 at position
dim.Merge a contiguous range of dimensions into one.
Return the view's access pattern as
[[stride, count], ...].Create an indirect tensor view for Tensor Indirection (TI).
Return True if the view covers storage contiguously (row-major order).
Return True if this view already uses indirect addressing.
Reorder tensor dimensions.
Rearrange tensor dimensions using einops-style patterns.
Alias for
view().Reshape the tensor to a new shape without copying data.
Split a single dimension into multiple dimensions.
Select a single element along a dimension, removing it.
Slice along a single dimension.
Remove a dimension of size 1.
Per-partition indirect addressing using a vector of offsets.
Reinterpret the tensor's data as a different dtype.
Attributes
The memory region the storage lives in.
Element type, e.g.
Optional debug label, used in compiler diagnostics and the profiler.
Number of dimensions (
len(shape)).Element offset (not byte offset) from the start of the underlying storage at which this view begins.
Tuple giving the count of each dimension of the view.
Total number of elements (
prod(shape)).Tuple with stride per dimension.
- ap(pattern: list[list[int | VirtualRegister]] | list[list[int]], offset: int | None = None, scalar_offset: NkiTensor | None = None, vector_offset: NkiTensor | None = None, indirect_dim: int = 0, dtype: DType | None = None) NkiTensor[source]#
Low-level access pattern override (escape hatch).
Replaces shape and strides with an explicit
[[stride, count], ...]pattern addressing the underlying storage directly. Analogous totorch.as_strided.Each
strideandcountslot may be a Pythonintor dynamic value only known at runtime —nki.isa.VirtualRegister. Note that when any slot is dynamic,scalar_offset/vector_offset/ non-zeroindirect_dimare not supported.sb = nl.ndarray((128, 32), dtype=nl.float32, buffer=nl.sbuf) # Explicit 2D pattern: partition stride=32, free stride=1 sb.ap(pattern=[[32, 128], [1, 32]]) # With indirect access and dtype reinterpret: sb.ap(pattern=[[64, 128], [1, 16]], dtype=nl.bfloat16, scalar_offset=idx, indirect_dim=1) # Register-bound stride (dynamic access pattern): stride0 = nisa.register_alloc(...) count1 = nisa.register_alloc(...) sb.ap(pattern=[[stride0, 128], [1, count1]])
- Parameters:
pattern – list of
[stride, count]pairs defining the access pattern. Each entry may beintorVirtualRegister.offset – element offset added to the view’s base storage offset. When
None(default), inherits the current view’s storage offset unchanged. Pass an explicit integer to compose with the base offset (e.g. offset=0 keeps the base, offset=N shifts by N additional elements).scalar_offset – dynamic scalar index for indirect access, as a 1x1 index tensor or an
NkiScalar. Must be 32-bit whenselfis in SBUF or PSUM; a 64-bit offset is only valid on an HBM operand, where it reaches past 4GBvector_offset – per-partition index tensor for indirect access
indirect_dim – dimension in
self.shapewhose stride scales the indirect scalar/vector offset (default 0)dtype – reinterpret storage as this dtype (default: tensor’s dtype)
- Returns:
new
NkiTensorwith the explicit access pattern
- broadcast(dim: int, size: int) NkiTensor[source]#
Expand a size-1 dimension to
sizeby repeating elements.The dimension must have size 1 before broadcasting. No data is copied.
t = nl.ndarray((128, 1, 64), dtype=nl.float32, buffer=nl.sbuf) t.broadcast(1, 8) # shape becomes (128, 8, 64)
Constraints.
t.shape[dim]must be 1On-chip tensors:
dimmust not be 0 (partition dim)
- Parameters:
dim – dimension to broadcast
size – new size for the dimension
- Returns:
new
NkiTensorview with the broadcasted dimension
- buffer: nki.language.MemoryRegion | str = Ellipsis#
The memory region the storage lives in. One of
nl.sbuf,nl.psum,nl.hbm,nl.private_hbm, ornl.shared_hbm.
- expand_dim(dim: int) NkiTensor[source]#
Insert a new dimension of size 1 at position
dim.t = nl.ndarray((128, 64), dtype=nl.float32, buffer=nl.sbuf) t.expand_dim(1) # shape becomes (128, 1, 64)
Constraints.
On-chip tensors:
dimmust not be 0After
vector_select:dimmust not be 0 (cannot insert before the indirect partition dim; seeis_indirect())
- Parameters:
dim – position at which to insert the new dimension
- Returns:
new
NkiTensorview with an additional size-1 dimension
- flatten_dims(start_dim: int, end_dim: int) NkiTensor[source]#
Merge a contiguous range of dimensions into one.
Dimensions
start_dimthroughend_dim(inclusive) are merged into a single dimension. The dimensions must already be contiguous in memory (nopermuteor non-contiguous slicing across them) so the merged view is itself a valid view of storage.t = nl.ndarray((128, 2, 3, 4), dtype=nl.float32, buffer=nl.sbuf) t.flatten_dims(1, 2) # shape becomes (128, 6, 4)
Constraints.
Dimensions
start_dim..end_dimmust be contiguous in memoryOn-chip tensors:
start_dimmust be > 0After
vector_select:start_dimmust be > 0
- Parameters:
start_dim – first dimension to merge (inclusive)
end_dim – last dimension to merge (inclusive)
- Returns:
new
NkiTensorview with the merged dimension
- get_pattern() list[list[int | VirtualRegister]][source]#
Return the view’s access pattern as
[[stride, count], ...].Useful as a starting point when constructing a new
.ap()that keeps most of the current layout intact. The returned pattern pairs each of the view’s dimensions with its current stride, in the same order asshape/strides.
- indirect(index: NkiTensor, num_elem: Optional[int] = None) NkiTensor[source]#
Create an indirect tensor view for Tensor Indirection (TI).
Available on NeuronCore-v4 (Trn3) and later.
TI allows reading or writing a column of data at given free-dimension offsets across contiguous partition dimensions. Can be used as input (gather) or output (scatter) in nisa operations.
Offsets are stored in a snake pattern across partition groups: offset i comes from
index[i % G, i // G]where G is the group size (16 for vector/scalar/gpsimd engines, 32 for tensor engine).- Parameters:
index – SBUF tensor containing free-dimension offsets, shape
(P, K)whereP == self.shape[0].num_elem – number of offsets to use. Defaults to
index.size.
- Returns:
new
NkiTensorview with TI attached. Output shape is(P, num_elem).
- is_contiguous() bool[source]#
Return True if the view covers storage contiguously (row-major order).
Computed from the current strides: each non-size-1 dimension’s stride must equal the product of the shape sizes of inner dimensions.
For on-chip tensors (SBUF, PSUM), the partition dim (dim 0) is skipped: partitions are physically independent memory banks, so the partition stride does not represent physical contiguity. Contiguity is evaluated per-partition over the free dims only.
- is_indirect() bool[source]#
Return True if this view already uses indirect addressing.
Indirect addressing is produced by dynamic
select(),vector_select(), orap()withscalar_offset/vector_offset. Indirect views cannot be re-indirected, and the dimension that participates in the indirection cannot be further sliced or selected — use this query to guard against those chains.
- offset: int = Ellipsis#
Element offset (not byte offset) from the start of the underlying storage at which this view begins.
- permute(dims: tuple[int, ...]) NkiTensor[source]#
Reorder tensor dimensions.
Returns a new view with dimensions rearranged according to
dims. No data is copied.t = nl.ndarray((128, 4, 8), dtype=nl.float32, buffer=nl.sbuf) t.permute((0, 2, 1)) # shape becomes (128, 8, 4)
Constraints.
dimsmust be a permutation ofrange(t.ndim)On-chip tensors:
dims[0]must be 0 (partition dim stays outermost)After
vector_select:dims[0]must be 0 (indirect partition dim stays outermost; seeis_indirect())
- Parameters:
dims – tuple of dimension indices in the desired order
- Returns:
new
NkiTensorview with reordered dimensions
- rearrange(src_pattern: tuple, dst_pattern: tuple, fixed_sizes: Optional[dict[str, int]] = None) NkiTensor[source]#
Rearrange tensor dimensions using einops-style patterns.
Combines splitting, reordering, and merging dimensions into a single named operation. Patterns are tuples of strings (dimension names) or tuples of strings (grouped dimensions that are split or merged).
t = nl.ndarray((128, 24), dtype=nl.float32, buffer=nl.sbuf) # Split dim 1 into (h, w), then reorder to (b, w, h): t.rearrange(('b', ('h', 'w')), ('b', 'w', 'h'), {'h': 4}) # Result shape: (128, 6, 4)
- Parameters:
src_pattern – source dimension pattern (tuple of str or tuple-of-str)
dst_pattern – destination dimension pattern (same dimension names)
fixed_sizes – dict mapping dimension names to known sizes (for -1 inference)
- Returns:
new
NkiTensorview with rearranged dimensions
- reinterpret_cast(dtype: str) NkiTensor[source]#
Alias for
view().- Parameters:
dtype – target NKI dtype to reinterpret as
- Returns:
new
NkiTensorview with the adjusted dtype and shape
- reshape(shape: tuple[int, ...]) NkiTensor[source]#
Reshape the tensor to a new shape without copying data.
The total number of elements must remain the same. Fails if the current memory layout is incompatible with the requested shape (e.g. after a non-contiguous slice or permute).
t = nl.ndarray((128, 4, 6), dtype=nl.float32, buffer=nl.sbuf) t.reshape((128, 24)) # merge last two dims t.reshape((128, 2, 12)) # split differently
Constraints.
prod(shape) == prod(t.shape)On-chip tensors:
shape[0]must equalt.shape[0](partition dim preserved)After
vector_select:shape[0]must equalt.shape[0](indirect partition dim preserved)Fails if the current layout is incompatible with the requested shape
- Parameters:
shape – tuple of new dimension sizes
- Returns:
new
NkiTensorview with the requested shape
- reshape_dim(dim: int, shape: tuple[int, ...]) NkiTensor[source]#
Split a single dimension into multiple dimensions.
The product of
shapemust equalt.shape[dim]. One element ofshapemay be -1, in which case its value is inferred.t = nl.ndarray((128, 24), dtype=nl.float32, buffer=nl.sbuf) t.reshape_dim(1, (4, 6)) # shape becomes (128, 4, 6) t.reshape_dim(1, (4, -1)) # same result, 6 is inferred
Constraints.
prod(shape) == t.shape[dim]On-chip tensors:
dimmust not be 0 (unlessshapeis trivial, e.g.,(128,))After
vector_select:dimmust not be 0
- Parameters:
dim – dimension to split
shape – tuple of sizes for the new dimensions (may contain one -1)
- Returns:
new
NkiTensorview with the dimension split
- select(dim: int, index: Union[int, NkiTensor, VirtualRegister, NkiScalar]) NkiTensor[source]#
Select a single element along a dimension, removing it.
The index is either a compile-time
int(equivalent tot[:, index, :]whendim=1) or a runtime value resolved on device: anNkiTensor1-element tile, aVirtualRegister, or anNkiScalar.t = nl.ndarray((128, 8, 64), dtype=nl.float32, buffer=nl.sbuf) t.select(1, 3) # static: shape becomes (128, 64) # Dynamic select (HBM tensor, index resolved at runtime): idx = nl.ndarray((1, 1), dtype=nl.uint32, buffer=nl.sbuf) hbm_t = nl.ndarray((4, 128, 8), dtype=nl.float32, buffer=nl.shared_hbm) hbm_t.select(0, idx) # shape becomes (128, 8) # Same thing driven by a register: reg = nisa.register_alloc() nisa.register_load(reg, idx) hbm_t.select(0, reg)
Constraints.
Static:
0 <= index < t.shape[dim]; runtime indices are not bounds-checked, and out-of-range is undefined behaviourOne runtime index per view (no chaining) — see
is_indirect()Runtime on-chip:
dimmust not be 0 (partition dim)Static selection cannot target a dimension already indirected
- Parameters:
dim – dimension to select from
index – compile-time
int, or a runtimeNkiTensor/VirtualRegister/NkiScalar
- Returns:
new
NkiTensorview with the dimension removed
- shape: tuple[int, ...] = Ellipsis#
Tuple giving the count of each dimension of the view. Each element is an integer, or a
VirtualRegisterfor dynamic-access views constructed viaap().
- size: int = Ellipsis#
Total number of elements (
prod(shape)).Nonewhen any element ofshapeis aVirtualRegister.
- slice(dim: int, start: int, end: int, step: int = 1) NkiTensor[source]#
Slice along a single dimension.
Returns a view selecting elements from
starttoend(exclusive) with the givenstep. Equivalent tot[:, start:end:step, :]whendim=1.t = nl.ndarray((128, 64), dtype=nl.float32, buffer=nl.sbuf) t.slice(1, 8, 24, 1) # shape becomes (128, 16) t.slice(1, 0, 64, 2) # shape becomes (128, 32)
Constraints.
0 <= start < end <= t.shape[dim]step >= 1On an indirect view (see
is_indirect()), cannot slice a dimension that participates in the indirection.
- Parameters:
dim – dimension to slice
start – start index (inclusive)
end – end index (exclusive)
step – step size (default 1)
- Returns:
new
NkiTensorview with the sliced dimension
- squeeze_dim(dim: int) NkiTensor[source]#
Remove a dimension of size 1.
t = nl.ndarray((128, 1, 64), dtype=nl.float32, buffer=nl.sbuf) t.squeeze_dim(1) # shape becomes (128, 64)
Constraints.
t.shape[dim]must be 1On-chip tensors:
dimmust not be 0After
vector_select:dimmust not be 0
- Parameters:
dim – dimension to remove (must have size 1)
- Returns:
new
NkiTensorview with the dimension removed
- strides: tuple[int, ...] = Ellipsis#
Tuple with stride per dimension. Each stride is the element step between consecutive indices along that dimension in the underlying storage. Each entry is an integer, or a
VirtualRegisterfor dynamic-access views constructed viaap(). A stride of0means the dimension is broadcast.
- vector_select(dim: int, vector_offset: NkiTensor) NkiTensor[source]#
Per-partition indirect addressing using a vector of offsets.
Each partition uses its own index from
vector_offsetas the base address for the selected dimension. The output partition count isvector_offset.shape[0].This is used for gather-style operations where different partitions read from different locations in the source tensor.
hbm_t = nl.ndarray((64, 128, 8), dtype=nl.float32, buffer=nl.shared_hbm) offsets = nl.ndarray((128, 1), dtype=nl.int32, buffer=nl.sbuf) # Each of 128 partitions reads from a different row of hbm_t hbm_t.vector_select(0, offsets) # shape becomes (128, 128, 8)
Constraints.
dimmust be 0Only supported on HBM tensors
Only one dynamic select per tensor (cannot combine with a prior dynamic
select()orvector_select()); checkis_indirect()to guardThe result is an indirect view: the selected dimension cannot be further sliced or selected.
- Parameters:
dim – dimension to apply indirect addressing (must be 0)
vector_offset – SBUF tensor with per-partition indices, shape
(num_partitions, 1)
- Returns:
new
NkiTensorview with dim 0 size set tovector_offset.shape[0]
- view(dtype: str) NkiTensor[source]#
Reinterpret the tensor’s data as a different dtype.
No data is copied. Equivalent to
numpy.ndarray.view(dtype)or C++reinterpret_cast. The last dimension’s size is scaled by the ratio of dtype sizes: reinterpreting a float32 tensor as uint8 multiplies the last-dim count by 4; reinterpreting uint8 as float32 divides it by 4.t = nl.ndarray((128, 64), dtype=nl.float32, buffer=nl.sbuf) t.view(nl.uint8) # shape becomes (128, 256), 4x expansion t.view(nl.int32) # shape stays (128, 64), same-size reinterpret u = nl.ndarray((128, 256), dtype=nl.uint8, buffer=nl.sbuf) u.view(nl.float32) # shape becomes (128, 64), 4x contraction
Constraints.
The last dimension must be contiguous in memory
For contraction (larger dtype): last-dim size must be divisible by the ratio
Not supported after dynamic / vector select
- Parameters:
dtype – target NKI dtype to reinterpret as
- Returns:
new
NkiTensorview with the adjusted dtype and shape
This document is relevant for: Trn2, Trn3