Data Model — Hurray Format Specification
Status: Draft
This section uses RFC 2119 key words: MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL.
Scope
This section defines the logical data model of a Hurray tensor: the abstract description of its shape and element structure, independent of how it is stored in memory or encoded on the wire. All other sections of the specification operate on the model defined here.
Tensor
A tensor is a multi-dimensional array of elements, all of the same element type. A tensor is characterised by:
- a rank — the number of dimensions,
- a shape — the size of each dimension,
- an element type — defined in
element-types.md.
The element type and shape fully determine the logical content of the tensor.
How the elements are mapped to memory is defined separately by the tensor's
memory layout (memory-layout.md).
Rank
The rank of a tensor is a non-negative integer that counts the number of
dimensions. It is encoded as a uint32 in the tensor descriptor (metadata.md).
- A rank-0 tensor is a scalar: it contains exactly one element and has no shape array.
- A rank-1 tensor is a vector.
- A rank-2 tensor is a matrix.
- Higher ranks are used for batched inputs, activation maps, and weight tensors in neural networks.
The maximum rank is 64. A writer MUST NOT emit a descriptor with rank > 64.
A reader MUST reject a descriptor with rank > 64. A conforming implementation
MUST support tensors of rank 0 through 64 inclusive (see
docs/adr/ADR-008-normative-rank-cap-64.md).
Note (non-normative): The
uint32rank field can encode values above 64, but those values are reserved and will be rejected. The cap matches PyTorch's MAX_DIMS = 64 and bounds the shape-array size at 512 bytes (64 × 8), enabling stack allocation on the descriptor-parsing hot path.
Shape
The shape of a tensor is an ordered sequence of rank dimension sizes,
indexed from 0. Dimension 0 is the outermost (slowest-varying) dimension
in row-major order; dimension rank − 1 is the innermost (fastest-varying).
Each dimension size is encoded as a uint64. A dimension size:
- MUST be greater than or equal to
0. - A size of
0denotes an empty dimension: the tensor contains no elements along that axis. The total element count of a tensor with any zero-size dimension is0. - The value
0xFFFFFFFFFFFFFFFF(UINT64_MAX) is the dynamic dimension sentinel — see § Dynamic Dimensions below. - Any other value in the range
[1, 0xFFFFFFFFFFFFFFFE]is a static dimension size.
For a scalar tensor (rank = 0), the shape is the empty sequence; no shape
bytes are present in the descriptor.
Total Element Count
For a tensor with no dynamic dimensions, the total number of logical elements is:
element_count = product(shape[i] for i in 0 .. rank-1)
This product is 1 for a scalar. For a tensor with one or more zero-size
dimensions, element_count = 0.
A reader MUST NOT compute element_count for a tensor that has any dynamic
dimension without first resolving all dynamic dimensions to concrete values.
Dynamic Dimensions
A dimension whose size is 0xFFFFFFFFFFFFFFFF is dynamic: its concrete
value is not known at descriptor-write time and MUST be supplied by the reader
or the interchange protocol before the tensor's data buffer can be safely
accessed.
Rules for dynamic dimensions:
- A reader MUST NOT compute buffer sizes, strides, or element counts for a tensor containing a dynamic dimension without first resolving it to a concrete value.
- A writer that sets a dimension to
0xFFFFFFFFFFFFFFFFMUST ensure the interchange channel provides a mechanism to communicate the resolved value before the data buffer is transferred (seeinterchange.md). - A quantization scheme that requires a statically known dimension size along
its quantization axis (
axis) MUST reject a descriptor whoseshape[axis]is0xFFFFFFFFFFFFFFFF. The specific constraint is documented per scheme inquantization/. - Shard descriptors MUST NOT use dynamic dimensions (see
metadata.md§ Shard Section).
Note (non-normative): Dynamic dimensions are intended for streaming and just-in-time dispatch scenarios, where a model producer does not know the batch size or sequence length at graph-compilation time. They are not a substitute for shape polymorphism at the type level.
Element Type
The element type of a tensor is identified by a uint8 type_tag defined
in element-types.md. All elements in a tensor share the same type tag.
The element type describes the storage representation of each element in
the data buffer. When a tensor is quantized (HAS_QUANTIZATION flag set), the
storage type is an integer or float8 type and the quantization descriptor
defines the mapping to real-valued elements. The storage type is always
orthogonal to the quantization scheme: type_tag never encodes quantization
semantics.
Scalar Tensors
A scalar tensor has rank = 0. Its shape is empty. It contains exactly one
element. The data buffer size is sizeof(element_type) bytes (or a partial
byte for sub-byte types; see element-types.md § Sub-Byte Packing).
A scalar tensor MUST NOT carry a strided, tiled, or sparse layout descriptor;
only row-major (0x01) is permitted for scalars.
Note (non-normative): Scalar tensors arise as outputs of reduction operations (e.g., loss values) and as single-element configuration parameters.
Empty Tensors
A tensor is empty if element_count = 0. This occurs when at least one
dimension has size 0. An empty tensor is valid; its data buffer has size 0
bytes.
An empty tensor MUST still carry a complete, valid descriptor with a correct element type, layout tag, buffer table, and any applicable quantization descriptor. A reader MUST accept an empty tensor without treating it as an error. A zero-length data buffer MAY be represented with a null pointer; the 64-byte alignment requirement does not apply to a zero-length buffer.
Note (non-normative): The decision to permit empty tensors is recorded in
docs/adr/ADR-007-permit-empty-tensors.md. The primary motivation is round-trip fidelity with PyTorch, NumPy, JAX, Apache Arrow, and DLPack, all of which permit zero-size dimensions.
Relationship to Other Sections
element-types.mddefines thetype_tagvalues, sub-byte packing rules, and the Tier 1 / Tier 2 type classification.memory-layout.mddefines how the logical index space described by the shape is mapped to a linear buffer: strides, block shapes, sparse indices.metadata.mddefines the binary encoding of rank, shape, and all other descriptor fields on the wire.quantization.mddefines how storage elements are mapped to real-valued elements whenHAS_QUANTIZATIONis set.interchange.mddefines how dynamic dimensions are resolved during runtime tensor exchange.
Open Questions
All open questions in this section are resolved. See docs/adr/ADR-007-permit-empty-tensors.md (OQ-2) and docs/adr/ADR-008-normative-rank-cap-64.md (OQ-1).