def ios18_lut_params_to_ios16(lut_params: LutParams) -> LutParamsIos16:
    """The iOS16 lut params pack indices into bytes, and need a `shape` parameter."""
    if len(lut_params.lut.shape) < 3:
        raise ValueError(
            "lut should have at least three dimensions, with shape [..., num_palettes, vector_size]."
        )
    for idx, dim_size in enumerate(lut_params.lut.shape[:-2]):
        if dim_size > 1:
            raise AssertionError(
                "The pre-iOS18 palettization only supports per-tensor lut, but got more than one "
                f"lut on {idx}th axis. LUT shape: {lut_params.lut.shape}"
                "\nPlease set the minimum_deployment_target to iOS18 or later."
            )
    if lut_params.lut.shape[-1] > 1:
        raise ValueError(
            "Vector palettization (lut last dim > 1) is only supported in iOS18+. "
            "Please set the minimum_deployment_target to iOS18 or later."
        )

    num_palettes = lut_params.lut.shape[-2]
    nbits = int(math.log2(num_palettes))
    return LutParamsIos16(
        lut=lut_params.lut.reshape((num_palettes,)),
        indices=pack_elements_into_bits(lut_params.indices, nbits),
        shape=np.array(
            lut_params.indices.shape, dtype=np.uint32
        ),  # The op requires shape parameter to be uint32.
    )
The iOS16 lut params pack indices into bytes, and need a `shape` parameter.
coremltools/optimize/_utils.py