This document is relevant for: Trn2, Trn3
nki.language.NkiTensor#
- class nki.language.NkiTensor(shape: tuple[int, ...], dtype: str, storage: Any, buffer: Any = Ellipsis, name: str = '')[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.
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
reinterpret_cast- ap(pattern: list[list[int]], offset: Optional[int] = None, scalar_offset: Optional[NkiTensor] = None, vector_offset: Optional[NkiTensor] = None, indirect_dim: int = 0, dtype=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.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)
- Parameters:
pattern – list of
[stride, count]pairs defining the access patternoffset – 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 tensor for indirect access
vector_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
- 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]][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.
- 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
- 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]) NkiTensor[source]#
Select a single element along a dimension, removing it.
When
indexis an integer, performs static selection (equivalent tot[:, index, :]whendim=1). Whenindexis anNkiTensor(e.g., a scalar loaded into SBUF), performs dynamic indirect selection where the index is resolved at runtime.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.int32, 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)
Constraints.
Static:
0 <= index < t.shape[dim]Dynamic: only one dynamic select per tensor (no chaining); check
is_indirect()to guardDynamic on-chip:
dimmust not be 0 (partition dim)On an indirect view (see
is_indirect()), static selection cannot target a dimension that participates in the indirection.
- Parameters:
dim – dimension to select from
index – integer index (static) or
NkiTensorscalar (dynamic)
- Returns:
new
NkiTensorview with the dimension removed
- 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
- 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) 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