tf.keras.quantizers.pack_int4

Pack an int4 tensor into an int8 tensor with packed nibbles.

The input values must already be int8 in the signed range `[-8, 7]` and
represent the desired int4 values. Packing is performed along the specified
axis (default is 0).

For every two consecutive rows, the **low nibble** of the output byte
stores the value from the first row, and the **high nibble** stores
the value from the second row.

Args:
    arr: An `int8` or `uint8` tensor containing int4 values in the range
        `[-8, 7]`.
    axis: The axis along which to pack the tensor. Defaults to 0.
    dtype: The data type of the input and packed tensor. Can be
        `"int8"` or `"uint8"`. Defaults to `"int8"`.

Returns:
    tuple: A tuple `(packed, packed_shape, orig_rows)` where `packed` is
        the packed int8 tensor with int4 values stored in nibbles,
        `packed_shape` is the shape of the packed tensor, and `orig_rows`
        is the original (unpacked) row count prior to any padding that may
        have been inserted when an odd number of rows is supplied.

Example:

```python
```
>>> import numpy as np
>>> from keras.quantizers import pack_int4, unpack_int4

```

# Example with axis=0
# Original array has shape (3, 2)
```
>>> original_array = np.array([[-3, 7], [2, -8], [1, 0]], dtype=np.int8)

```

# Pack the array along axis 0. Since the length of axis 0 (3) is
# odd, it will be padded to a length of 4. The packed array will
# have a shape of (ceil(3/2), 2) = (2, 2).
```
>>> packed, packed_shape, orig_len = pack_int4(original_array, axis=0)
>>> print("Packed array:

", packed) Packed array: [[ 45 -121] [ 1 0]]

```

# Now, unpack the array back to its original form
```
>>> unpacked = unpack_int4(packed, orig_len, axis=0)
>>> print("Unpacked array:

", unpacked) Unpacked array: [[-3 7] [ 2 -8] [ 1 0]] >>> np.allclose(original_array, unpacked) True

```

# Example with axis=1
# Original array has shape (2, 3)
```
>>> original_array = np.array([[-3, 7, 2], [-8, 1, 0]], dtype=np.int8)

```

# Pack along axis 1. Length of axis 1 (3) is padded to 4.
# The new shape is (2, ceil(3/2)) = (2, 2).
```
>>> packed, packed_shape, orig_len = pack_int4(original_array, axis=1)
>>> print("Packed array:

", packed) Packed array: [[ 125 2] [ 24 0]]

```

# Unpack the array
```
>>> unpacked = unpack_int4(packed, orig_len, axis=1)
>>> print("Unpacked array:

", unpacked) Unpacked array: [[-3 7 2] [-8 1 0]] >>> np.allclose(original_array, unpacked) True

    ```

```