(l-onnx-doc-Unpack)= # Unpack (l-onnx-op-unpack-29)= ## Unpack - 29 ### Version - **name**: [Unpack (GitHub)](https://github.com/onnx/onnx/blob/main/docs/Operators.md#Unpack) - **domain**: `main` - **since_version**: `29` - **function**: `True` - **support_level**: `SupportType.COMMON` - **shape inference**: `True` This version of the operator has been available **since version 29**. ### Summary Unpacks UINT8 or UINT32 storage words into UINT8 unsigned integer codes independently along `axis` (default -1). Input and output have the same rank, at least 1. Negative axes are supported; axis must be in [-rank, rank-1]. The input type determines word_bits: 8 for UINT8, 32 for UINT32. Specify either `bits` in [1, 8], or both `radix` and `group_size`; the two spellings are mutually exclusive. The input `count` must be a nonnegative scalar INT64 tensor specifying the number of output codes along axis, excluding padding. With `bits`, each element of `Y` is in [0, 2^bits - 1]. When using `bits`, the input axis dimension B must equal ceil(count * bits / word_bits). The output axis dimension is count; all other dimensions are preserved. Each fixed combination of indices in the other dimensions defines an independent sequence, called a row below; a rank-1 tensor has one row. Within each row, input words are read in increasing axis index order, starting with the least significant bit of each word. Consecutive groups of `bits` bits form output codes in order, with the first bit of each group as the code's least significant bit. Number the bits of each word and code starting at 0 for the least significant bit. For code index `0 <= i < count` and bit index `0 <= k < bits`, bit `k` of output code `i` is read from bit `(i * bits + k) % word_bits` of input word `floor((i * bits + k) / word_bits)`. When a code spans a word boundary, its higher-order bits are read from the low bits of the next word. Each row begins at bit 0 of a new word. Unused high bits in the final input word of each row are padding and are ignored. Bit positions are defined numerically, independently of host endianness; tensor raw data uses ONNX's little-endian serialization. All non-axis dimensions are preserved, including zero dimensions, and the same shape requirements apply when any non-axis dimension is zero. With `count = 0`, both input and output must have an empty selected axis. With UINT8 storage, `bits = 3` and `count = 8` unpack `[136, 198, 250]` into `[0, 1, 2, 3, 4, 5, 6, 7]`. With `bits = 3` and `count = 2`, `[255]` is unpacked into `[7, 7]`. Grouped unpacking uses radix R in [2, 256] and positive group size G, with R^G <= 2^63 - 1. Read consecutive W = ceil(log2(R^G)) bit group indices, least-significant bit first, without word alignment between groups. R^G determines the precision of the integer arithmetic required for both packing and unpacking: valid group indices require W value bits, and any explicitly computed R^G or other intermediate must also fit. Prefer (R, G) pairs supported by standard 32-bit or 64-bit integer arithmetic. The R^G bound above permits exact signed 64-bit base-radix calculations. A commonly used choice is (R, G) = (3, 5): five ternary codes per byte. Every group index must be less than R^G; encodings outside this range are invalid. Expand each index into G base-R digits, least significant digit first, then trim to count values per row. The input axis dimension must equal ceil(ceil(count/G) * W / word_bits). Unused digits of the last group and unused high bits of the last word are ignored. Each output code is in [0, R - 1]. The original bits spelling is (R, G) = (2^bits, 1). With UINT8 storage and R=3, G=5, [102] with count=5 becomes [0, 1, 2, 0, 1]; [7] with count=2 becomes [1, 2]. Results for invalid inputs are unspecified; function decompositions need not diagnose invalid indices or incorrect input sizes. ### Attributes * **axis - INT** (default is `-1`): Axis along which independent sequences are unpacked. Negative axes are supported. * **bits - INT** : Number of bits per unsigned code. Mutually exclusive with radix/group_size. * **group_size - INT** : Codes per group. Positive; radix^group_size must fit in positive INT64. * **radix - INT** : Base of grouped codes, in [2, 256]. Requires group_size; excludes bits. ### Inputs - **X** (heterogeneous) - **TStorage**: UINT8 or UINT32 tensor of rank at least 1, with the exact packed word count along axis. - **count** (heterogeneous) - **tensor(int64)**: Scalar INT64 tensor giving the logical output length along axis. Must be nonnegative. ### Outputs - **Y** (heterogeneous) - **T**: UINT8 tensor preserving every dimension except axis, which becomes count. Each element is below the selected radix. ### Type Constraints * **T** in ( `tensor(uint8)` ): Unsigned byte tensors. * **TStorage** in ( `tensor(uint32)`, `tensor(uint8)` ): Unsigned storage word tensors. ### Examples #### _unpack_uint32_axis ```python import numpy as np import onnx node = helper.make_node("Unpack", ["X", "count"], ["Y"], bits=5, axis=0) x = np.full((2, 2), 0xFFFFFFFF, dtype=np.uint32) count = np.array(7, dtype=np.int64) y = np.full((7, 2), 31, dtype=np.uint8) expect(node, inputs=[x, count], outputs=[y], name="test_unpack_uint32_axis") ``` #### _unpack_ternary ```python import numpy as np import onnx node = helper.make_node("Unpack", ["X", "count"], ["Y"], radix=3, group_size=5) x = np.array([[102], [242]], dtype=np.uint8) count = np.array(5, dtype=np.int64) y = np.array([[0, 1, 2, 0, 1], [2, 2, 2, 2, 2]], dtype=np.uint8) expect(node, inputs=[x, count], outputs=[y], name="test_unpack_ternary") ``` #### _unpack_3bit ```python import numpy as np import onnx node = helper.make_node("Unpack", ["X", "count"], ["Y"], bits=3) x = np.array([0x88, 0xC6, 0xFA], dtype=np.uint8) count = np.array(8, dtype=np.int64) y = np.arange(8, dtype=np.uint8) expect(node, inputs=[x, count], outputs=[y], name="test_unpack_3bit") ``` #### _unpack_rows_5bit ```python import numpy as np import onnx node = helper.make_node("Unpack", ["X", "count"], ["Y"], bits=5) x = np.array([[0x41, 0x0C], [0x1F, 0x40]], dtype=np.uint8) count = np.array(3, dtype=np.int64) y = np.array([[1, 2, 3], [31, 0, 16]], dtype=np.uint8) expect(node, inputs=[x, count], outputs=[y], name="test_unpack_rows_5bit") ``` #### _unpack_empty ```python import numpy as np import onnx node = helper.make_node("Unpack", ["X", "count"], ["Y"], bits=3) x = np.empty((2, 0), dtype=np.uint8) count = np.array(0, dtype=np.int64) expect(node, inputs=[x, count], outputs=[x], name="test_unpack_empty") ``` #### _unpack_empty_rows ```python import numpy as np import onnx node = helper.make_node("Unpack", ["X", "count"], ["Y"], bits=5) x = np.empty((2, 0, 6), dtype=np.uint8) count = np.array(9, dtype=np.int64) y = np.empty((2, 0, 9), dtype=np.uint8) expect(node, inputs=[x, count], outputs=[y], name="test_unpack_empty_rows") ```