# Tensors and Views

> NumKong for Python

`Tensor` is a memoryview-backed object with NumPy-like metadata.
It is the central container for strided views, transpose, reshape, flatten, and axis reductions.

```python
import numpy as np
import numkong as nk

t = nk.Tensor(np.arange(12, dtype=np.float32).reshape(3, 4))

print(t.shape, t.dtype, t.ndim, t.strides, t.itemsize, t.nbytes)
print(np.asarray(t))      # zero-copy array view when layout allows it
print(t.T.shape)          # transposed Tensor view
print(t.reshape(2, 6).shape)
print(t.flatten().shape)

# Slicing — row, column, and scalar access
row0 = t[0, :]            # first row, shape (4,)
col2 = t[:, 2]            # third column, strided view, shape (3,)
val  = t[1, 2]            # scalar element access → 6.0

# Reductions compose with sliced views
idx = col2.argmin()        # index of the minimum in the third column
mn, i0, mx, i1 = col2.minmax()
```

The important layout rules are:

- `Tensor` preserves shape and byte strides.
- Transpose and slicing can produce non-contiguous views.
- General reductions accept those views.
- Matrix-style packed kernels require row-contiguous left operands.
- Packed and symmetric outputs require C-contiguous `out` buffers.

### Memory Layout Requirements

| API family                                       | Input requirement                                                                                  | Output requirement                                                        |
| :----------------------------------------------- | :------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------ |
| Dense distances (`dot`, `euclidean`, etc.)       | Rows must be contiguous (`strides[last] <= itemsize`). Strided rows (sliced columns) are rejected. | `out=` can have any stride along dim 0, but inner dim must be contiguous. |
| `cdist`                                          | Same as dense distances                                                                            | `out=` must be rank-2 with shape `(a.count, b.count)`                     |
| Elementwise (`scale`, `blend`, `fma`)            | Arbitrary strides (strided views are supported)                                                    | `out=` must match input shape; strides are preserved                      |
| Packed matrix (`dots_packed`)                    | Left operand: rank-2, contiguous rows, no negative strides                                         | Output: C-contiguous with expected dtype                                  |
| Symmetric (`dots_symmetric`)                     | Contiguous rows                                                                                    | `out=`: C-contiguous square matrix                                        |
| Tensor reductions (`sum`, `min`, `argmin`, etc.) | Arbitrary strides (strided views supported)                                                        | N/A (returns scalar or reduced tensor)                                    |

### GPU Tensors through DLPack

Builds configured with `-C cmake.define.NUMKONG_CUDA_ARCH=all` or `-C cmake.define.NUMKONG_ROCM_ARCH=all` also carry the CUDA or ROCm kernels.
`nk.cuda_count_devices()` counts the devices this process sees, zero without the runtime or its kernels, and `nk.cuda_capabilities_enabled(0)` reports the mask device 0 dispatches with, raising `ValueError` past the last.
`nk.cuda_capabilities_detected` and `nk.cuda_capabilities_compiled` answer as their CPU twins do, `nk.rocm_*` and `nk.metal_*` do the same for the other vendors, and `cpu_configure_thread` is CPU only.
`nk.from_dlpack` imports a CUDA or ROCm tensor without a copy and records its device.
Only `dots_pack`, the dots, angulars and euclideans `_packed` and `_symmetric` functions, and `attention_pack`, `attention_packed` and `attention_packed_gradients` run on it, with that device's enabled capabilities.
The shape functions also take it for their outputs, running the mask their packs were copied with.
Element access, NumPy conversion, `cdist`, `maxsim` and every other CPU kernel raise `BufferError` rather than read device memory.
No verb allocates, so every GPU call writes into an `out=` its device reaches.
The dots and attention families take that as a Tensor on the same device, and mixing devices raises `ValueError`.
The shape functions take any memory the device reaches, a Tensor or a buffer over managed or pinned memory, which the runtime checks per call, raising `BufferError` for the rest.
Unified memory comes only from an explicit `copy`, as in `nk.shapes_pack(clouds).copy(capabilities=gpu)` and `nk.zeros(shape).copy(capabilities=gpu)`, which frees with the tensor and its views and refuses `resize()` and `reserve()`.
A GPU call queues one launch on `stream=`, an integer handle defaulting to 0, the legacy stream, and returns before it finishes, as PyTorch does, and `nk.synchronize(capabilities, stream=stream)` waits for it.
`nk.stream_init_cuda(ordinal)` makes such a handle on any device by its ordinal, and `nk.stream_free_cuda(stream)` frees it once synchronized, with `rocm_` and `metal_` twins.
Metal tensors are host-readable and keep running on the CPU kernels, except the shape functions, which run on Metal over blocks of its allocator.

```python
import torch
import numkong as nk

left = torch.randn(4096, 768, device="cuda", dtype=torch.bfloat16)
right = torch.randn(8192, 768, device="cuda", dtype=torch.bfloat16)
capabilities = nk.cuda_capabilities_enabled(right.device.index)
packed_storage = torch.empty(nk.PackedMatrix.pack_size(8192, 768, "bf16", capabilities=capabilities),
                             device="cuda", dtype=torch.uint8)
result = torch.empty(4096, 8192, device="cuda", dtype=torch.float32)

stream = torch.cuda.current_stream().cuda_stream
packed = nk.dots_pack(nk.from_dlpack(right), out=nk.from_dlpack(packed_storage), stream=stream)
nk.dots_packed(nk.from_dlpack(left), packed, out=nk.from_dlpack(result), stream=stream)
nk.synchronize(capabilities, stream=stream)
```

`import numkong`

`from numkong import Tensor, ScaledTensor`

## Tensor

N-dimensional tensor type returned by NumKong operations.

```python
class Tensor(memoryview)
```

Supports NumPy-like properties and buffer protocol for interoperability.
Packed dtypes 'uint1', 'int4', 'uint4', and 'e2m1' count logical dimensions in [`shape`](https://www.unum.cloud/docs/numkong/python/mesh-alignment.md#shape);
the last dimension must be a multiple of the values per byte, and the buffer protocol,
[`__array_interface__`](#__array_interface__), [`nbytes`](https://www.unum.cloud/docs/numkong/python/mesh-alignment.md#nbytes), and DLPack expose whole bytes.

## Tensor

Construct a Tensor by copying data from a buffer-protocol object; packed dtypes read its bytes.

```python
Tensor(
    array_like: _BufferType,
    /,
    *,
    dtype: str | _MiniFloatType | None = None,
)
```

## __len__

Return the length of the first dimension.

```python
def __len__() -> int
```

## __iter__

Iterate over the first dimension.

```python
def __iter__() -> Any
```

## __getitem__

Get an element or sub-tensor by index or slice.

```python
def __getitem__(
    key: int | slice | tuple[int | slice, ...],
) -> float | Tensor
```

## __repr__*__repr__

Return a string representation.

```python
def __repr____repr__() -> str
```

## __*__

Convert 0D tensor to Python float.

```python
def __float__() -> float
```

- `__float__`
- `__int__`

## __str__

Return a pretty-printed string representation.

```python
def __str__() -> str
```

## __eq__

Compare tensors for equality.

```python
def __eq__(other: Any) -> bool
```

## __ne__

Compare tensors for inequality.

```python
def __ne__(other: Any) -> bool
```

## __add__

Element-wise sum with another same-shape Tensor, or a broadcast scalar.

```python
def __add__(other: Tensor | float | int) -> Tensor
```

## __sub__

Element-wise difference with another same-shape Tensor, or a broadcast scalar.

```python
def __sub__(other: Tensor | float | int) -> Tensor
```

## __mul__

Element-wise product with another same-shape Tensor, or a broadcast scalar.

```python
def __mul__(other: Tensor | float | int) -> Tensor
```

## __matmul__

Dot-product matrix multiplication against a pre-packed matrix; see [`dots_packed`](https://www.unum.cloud/docs/numkong/python/matrix-kernels.md#dots_packed).

```python
def __matmul__(other: PackedMatrix) -> Tensor
```

## __neg__

Return the element-wise negation.

```python
def __neg__() -> Tensor
```

## __pos__

Return a copy of the tensor.

```python
def __pos__() -> Tensor
```

## __dlpack__

Return a DLPack capsule for zero-copy exchange with PyTorch, JAX, NumPy, etc.

```python
def __dlpack__(
    *,
    stream: Any | None = None,
    max_version: tuple[int, int] | None = None,
    dl_device: tuple[int, int] | None = None,
    copy: bool | None = None,
) -> Any
```

## __dlpack_device__

Return the DLPack device tuple.

```python
def __dlpack_device__() -> tuple[int, int]
```

Always (1, 0) (kDLCPU).

## __array__

Convert to a NumPy array, sharing data when possible.

```python
def __array__(
    dtype: str | None = None,
    *,
    copy: bool | None = None,
) -> Any
```

## copy

Return a deep copy of the tensor, or write it into `out` and return `out`.

```python
def copy(
    *,
    out: Tensor | None = None,
    capabilities: Capability | int | None = None,
    stream: int | None = None,
) -> Tensor
```

## reshape

Return tensor reshaped to given dimensions.

```python
def reshape(*shape: int = ()) -> Tensor
```

## flatten

Return a flattened 1D view, or a copy if non-contiguous or `out` is given.

```python
def flatten(*, out: Tensor | None = None) -> Tensor
```

## squeeze

Remove the given size-1 dimensions, or every size-1 dimension when `axes` is empty.

```python
def squeeze(*axes: int = ()) -> Tensor
```

## astype

Return a copy cast to the given dtype, quantizing to a ScaledTensor for a block-scaled target.

```python
def astype(
    dtype: Literal['uint1', 'int4', 'uint4', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8'] | _FloatTypeName | _ComplexTypeName | _MiniFloatType | _BlockScaledTypeName,
    /,
    *,
    out: Tensor | None = None,
) -> Tensor | ScaledTensor
```

## resize

Reshape in place within capacity without moving storage; returns self.

```python
def resize(*shape: int = ()) -> Tensor
```

## reserve

Grow the allocated capacity to at least [`capacity`](#capacity) elements (may move); preserves contents.

```python
def reserve(capacity: int) -> None
```

## clear

Reset to an empty shape (size 0) while keeping the allocated capacity.

```python
def clear() -> None
```

## sum

Return the sum of all elements.

```python
def sum(
    axis: int | tuple[int, ...] | None = None,
    *,
    keepdims: bool = False,
    out: Tensor | None = None,
    capabilities: Capability | int | None = ...,
    stream: int | None = ...,
) -> float | int | Tensor
```

- `capabilities`: Capabilities to run within: every CPU one by default, or for packed operands the mask that packed them.
- `stream`: A GPU stream pointer, left None on the CPU.

## norm

Return the L2 norm.

```python
def norm(
    axis: int | tuple[int, ...] | None = None,
    *,
    keepdims: bool = False,
    out: Tensor | None = None,
    capabilities: Capability | int | None = ...,
    stream: int | None = ...,
) -> float | Tensor
```

- `capabilities`: Capabilities to run within: every CPU one by default, or for packed operands the mask that packed them.
- `stream`: A GPU stream pointer, left None on the CPU.

## min

Return the minimum element, or None if all elements are NaN.

```python
def min(
    axis: int | tuple[int, ...] | None = None,
    *,
    keepdims: bool = False,
    out: Tensor | None = None,
    capabilities: Capability | int | None = ...,
    stream: int | None = ...,
) -> float | int | Tensor | None
```

- `capabilities`: Capabilities to run within: every CPU one by default, or for packed operands the mask that packed them.
- `stream`: A GPU stream pointer, left None on the CPU.

## max

Return the maximum element, or None if all elements are NaN.

```python
def max(
    axis: int | tuple[int, ...] | None = None,
    *,
    keepdims: bool = False,
    out: Tensor | None = None,
    capabilities: Capability | int | None = ...,
    stream: int | None = ...,
) -> float | int | Tensor | None
```

- `capabilities`: Capabilities to run within: every CPU one by default, or for packed operands the mask that packed them.
- `stream`: A GPU stream pointer, left None on the CPU.

## argmin

Return the index of the minimum element, or None if all elements are NaN.

```python
def argmin(
    axis: int | None = None,
    *,
    keepdims: bool = False,
    out: Tensor | None = None,
    capabilities: Capability | int | None = ...,
    stream: int | None = ...,
) -> int | Tensor | None
```

- `capabilities`: Capabilities to run within: every CPU one by default, or for packed operands the mask that packed them.
- `stream`: A GPU stream pointer, left None on the CPU.

## argmax

Return the index of the maximum element, or None if all elements are NaN.

```python
def argmax(
    axis: int | None = None,
    *,
    keepdims: bool = False,
    out: Tensor | None = None,
    capabilities: Capability | int | None = ...,
    stream: int | None = ...,
) -> int | Tensor | None
```

- `capabilities`: Capabilities to run within: every CPU one by default, or for packed operands the mask that packed them.
- `stream`: A GPU stream pointer, left None on the CPU.

## shape

Shape of the tensor as a tuple of logical dimensions.

```python
shape: tuple[int, ...]
```

## dtype

Data type of the tensor elements (e.g., 'float64').

```python
dtype: Literal['uint1', 'int4', 'uint4', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8'] | _FloatTypeName | _ComplexTypeName
```

## ndim

Number of dimensions.

```python
ndim: int
```

## size

Total number of elements.

```python
size: int
```

## nbytes

Total number of bytes.

```python
nbytes: int
```

## strides

Strides of the tensor in bytes.

```python
strides: tuple[int, ...]
```

## itemsize

Size of each element in bytes.

```python
itemsize: int
```

## __array_interface__

NumPy array interface dict for legacy interoperability.

```python
__array_interface__: dict[str, Any]
```

## T

Transpose of the tensor.

```python
T: Tensor
```

## is_contiguous

Whether the tensor is C-contiguous in memory.

```python
is_contiguous: bool
```

## data_ptr

Integer address of the underlying data buffer.

```python
data_ptr: int
```

## capacity

Allocated element capacity of the owned buffer (>= size).

```python
capacity: int
```

## ScaledTensor

Block-scaled tensor: the OCP MX family plus NVIDIA NVFP4.

```python
class ScaledTensor
```

Holds packed elements, per-block scales, and an optional per-tensor scale.
Produced by `Tensor.astype('nvfp4')`, `Tensor.astype('mxfp4')`, and similar block-scaled targets.

## astype

Materialize to a dense dtype, or transcode to another block-scaled dtype.

```python
def astype(
    dtype: Literal['uint1', 'int4', 'uint4', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8'] | _FloatTypeName | _ComplexTypeName | _MiniFloatType | _BlockScaledTypeName,
    /,
) -> Tensor | ScaledTensor
```

## resize

Resize the packed elements and block scales in lockstep, within capacity; returns self.

```python
def resize(*shape: int = ()) -> ScaledTensor
```

## __getitem__

Slice elements and block_scales in lockstep; the last axis must stay block-aligned.

```python
def __getitem__(
    key: int | slice | tuple[int | slice, ...],
) -> ScaledTensor
```

## elements

Packed sub-byte element Tensor, DLPack-exportable.

```python
elements: Tensor
```

## block_scales

Per-block scale Tensor, DLPack-exportable.

```python
block_scales: Tensor
```

## tensor_scale

Per-tensor float multiplier, None for MX formats.

```python
tensor_scale: float | None
```

## block_size

Number of elements per block.

```python
block_size: int
```

## dtype

Composite block-scaled dtype name.

```python
dtype: _BlockScaledTypeName
```

## shape

Logical, dense shape of the quantized tensor.

```python
shape: tuple[int, ...]
```

## capacity

Allocated element capacity of the packed elements buffer.

```python
capacity: int
```

## from_pointer

Create a zero-copy Tensor view over memory at a raw address.

```python
def from_pointer(
    address: int,
    shape: int | tuple[int, ...],
    dtype: _DTypeLike,
    /,
    *,
    strides: tuple[int, ...] | None = None,
    owner: Any = None,
) -> Tensor
```

- `address`: Integer address of the data buffer.
- `shape`: Shape of the view, an int or a tuple of ints.
- `dtype`: Element dtype, like 'float32' or 'bf16'.
- `strides`: Byte strides, C-contiguous when None.
- `owner`: Object the view keeps alive, so the memory it owns is not collected.

## from_dlpack

Zero-copy import a tensor from any DLPack-compatible producer.

```python
def from_dlpack(obj: Any, /) -> Tensor
```

Accepts a PyCapsule named 'dltensor' or 'dltensor_versioned', or any object
with a [`__dlpack__`](#__dlpack__) method (e.g. [`torch.Tensor`](#Tensor), `numpy.ndarray`,
`jax.Array`). Only CPU tensors are supported.

## empty

Create an uninitialized Tensor of the given shape and dtype.

```python
def empty(
    shape: int | tuple[int, ...],
    /,
    *,
    dtype: Literal['uint1', 'int4', 'uint4', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8'] | _FloatTypeName | _ComplexTypeName | _MiniFloatType = 'float32',
) -> Tensor
```

## *

Create a Tensor of the given shape and dtype filled with zeros.

```python
def zeros(
    shape: int | tuple[int, ...],
    /,
    *,
    dtype: Literal['uint1', 'int4', 'uint4', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8'] | _FloatTypeName | _ComplexTypeName | _MiniFloatType = 'float32',
) -> Tensor
```

- `zeros`
- `ones`

## full

Create a Tensor of the given shape and dtype filled with `fill_value`.

```python
def full(
    shape: int | tuple[int, ...],
    fill_value: int | float,
    /,
    *,
    dtype: Literal['uint1', 'int4', 'uint4', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8'] | _FloatTypeName | _ComplexTypeName | _MiniFloatType = 'float32',
) -> Tensor
```

## iota

Tensor filled with `seed, seed+1, seed+2, ...` reshaped to [`shape`](https://www.unum.cloud/docs/numkong/python/mesh-alignment.md#shape).

```python
def iota(
    shape: int | tuple[int, ...],
    /,
    seed: int | float = 0,
    *,
    dtype: Literal['uint1', 'int4', 'uint4', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8'] | _FloatTypeName | _ComplexTypeName | _MiniFloatType = 'float32',
) -> Tensor
```

## diagonal

Square `n x n` tensor with `seed` on the main diagonal and zeros elsewhere.

```python
def diagonal(
    n: int,
    /,
    seed: int | float = 1,
    *,
    dtype: Literal['uint1', 'int4', 'uint4', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8'] | _FloatTypeName | _ComplexTypeName | _MiniFloatType = 'float32',
) -> Tensor
```

## hash

Tensor filled with deterministic pseudo-random bits (shape, dtype, seed → reproducible bytes).

```python
def hash(
    shape: int | tuple[int, ...],
    /,
    seed: int = 0,
    *,
    dtype: Literal['uint1', 'int4', 'uint4', 'int64', 'int32', 'int16', 'int8', 'uint64', 'uint32', 'uint16', 'uint8'] | _FloatTypeName | _ComplexTypeName | _MiniFloatType = 'float32',
) -> Tensor
```

## astype

Cast an N-D buffer to [`dtype`](https://www.unum.cloud/docs/numkong/python/mesh-alignment.md#dtype) without first copying it into a Tensor.

```python
def astype(
    a: _BufferType,
    dtype: _DTypeLike,
    /,
    *,
    out: _BufferType | None = None,
) -> Tensor
```

A supplied `out` must be writable, C-contiguous, have the exact input
shape and requested dtype, and must not overlap `a`. Returns `out` when
provided, otherwise a new Tensor.

