def linear_quantize_activations(
    mlmodel: "_model.MLModel",
    config: _OptimizationConfig,
    sample_data: List[Dict[Optional[str], np.ndarray]],
    calibration_op_group_size: int = -1,
):
    """
    Utility function to convert a float precision MLModel of type ``mlprogram``, which uses
    float-precision activations, into a compressed MLModel that uses n-bit activations. Currently, only n=8
    is suppported.

    This is achieved by feeding real sample data into the input MLModel, calibrating the resulting float activation values,
    converting the calibrated values into ``quantize`` and ``dequantize`` op pairs, and inserting those
    op pairs into the new MLModel instance where activations get quantized.

    Use this function with ``linear_quantize_weights`` for 8-bit activation and 8-bit weight linear quantization.
    It's also compatible for use with other weight compression methods.

    Parameters
    ----------
    mlmodel: MLModel
        Model to be quantized. This MLModel should be of type ``mlprogram``.

    config: OptimizationConfig
        An :py:class:`OptimizationConfig` object that specifies the parameters for activation quantization.

    sample_data: List
        Data used to characterize statistics of the activation values of the original float precision model.
        Expects a list of sample input dictionaries, which should have the same format as the data used in `.predict`
        method for the mlmodel. More specifically, the input name need to be specified in the data, unless it's a single
        input model where the name will be auto inferred.

    calibration_op_group_size: int
        While running inference during calibration, only have `calibration_op_group_size` of intermediate outputs
        appended to outputs at a time. If the model is very large, it could lead to the temperary model having
        thousands of outputs, which may lead to model hanging forever during model loading. To work around this
        issue, intermediate outputs are grouped into smaller groups, where each time a temperary model will only
        have `calibration_op_group_size` outputs. By default (op_group_size = -1), op_group_size is equal to the
        number of valid intermediate ops.

    Returns
    -------
    model: MLModel
        The activation quantized MLModel instance.

    Examples
    --------
    .. sourcecode:: python

        import coremltools as ct
        import coremltools.optimize as cto

        model = ct.coreml.models.MLModel("my_model.mlpackage")
        activation_config = cto.coreml.OptimizationConfig(
            global_config=cto.coreml.OpLinearQuantizerConfig(mode="linear_symmetric")
        )
        compressed_model_a8 = cto.coreml.linear_quantize_activations(
            model, activation_config, sample_data
        )

        # (Optional) It's recommended to use with linear_quantize_weights.
        weight_config = cto.coreml.OptimizationConfig(
            global_config=cto.coreml.OpLinearQuantizerConfig(mode="linear_symmetric")
        )
        compressed_model_w8a8 = cto.coreml.linear_quantize_weights(compressed_model_a8, weight_config)
    """
    # Validate Sample data. If the sample data name is not provided, try to infer it.
    for sample in sample_data:
        if None in sample.keys():
            input_spec = mlmodel.get_spec().description.input
            if len(sample.keys()) > 1 or len(input_spec) > 1:
                raise ValueError(
                    "When the model has multiple inputs, please provide the name for each data in `sample_data`"
                )
            inferred_input_name = input_spec[0].name
            sample[inferred_input_name] = sample[None]
            del sample[None]

    graph_passes = []

    # Insert prefix quantize/dequantize pairs to valid patterns.
    insert_prefix_quantize_dequantize_pair = PASS_REGISTRY[
        "compression::insert_prefix_quantize_dequantize_pair"
    ]
    insert_prefix_quantize_dequantize_pair.set_options([PassOption("config", config)])
    activation_stats = _get_activation_calibration_stats(mlmodel, sample_data)
    insert_prefix_quantize_dequantize_pair.set_options(
        [PassOption("activation_stats", activation_stats)]
    )
    graph_passes.append(insert_prefix_quantize_dequantize_pair)

    # Insert suffix quantize/dequantize pairs to valid patterns.
    insert_suffix_quantize_dequantize_pair = PASS_REGISTRY[
        "compression::insert_suffix_quantize_dequantize_pair"
    ]
    insert_suffix_quantize_dequantize_pair.set_options([PassOption("config", config)])
    insert_suffix_quantize_dequantize_pair.set_options(
        [PassOption("activation_stats", activation_stats)]
    )
    graph_passes.append(insert_suffix_quantize_dequantize_pair)

    # Re-use exsiting path to dedup quantize/dequantize operations.
    graph_passes.append(PASS_REGISTRY["common::dequantize_quantize_pair_elimination"])

    mlmodel_activation_quantized = _model_utils._apply_graph_pass(
        mlmodel,
        graph_passes,
        spec_version=_SPECIFICATION_VERSION_IOS_17,
        pymil_load_func=_milproto_to_pymil.load,
        skip_model_load=mlmodel.__proxy__ is None,
    )

    return mlmodel_activation_quantized

    Utility function to convert a float precision MLModel of type ``mlprogram``, which uses
    float-precision activations, into a compressed MLModel that uses n-bit activations. Currently, only n=8
    is suppported.

    This is achieved by feeding real sample data into the input MLModel, calibrating the resulting float activation values,
    converting the calibrated values into ``quantize`` and ``dequantize`` op pairs, and inserting those
    op pairs into the new MLModel instance where activations get quantized.

    Use this function with ``linear_quantize_weights`` for 8-bit activation and 8-bit weight linear quantization.
    It's also compatible for use with other weight compression methods.

    Parameters
    ----------
    mlmodel: MLModel
        Model to be quantized. This MLModel should be of type ``mlprogram``.

    config: OptimizationConfig
        An :py:class:`OptimizationConfig` object that specifies the parameters for activation quantization.

    sample_data: List
        Data used to characterize statistics of the activation values of the original float precision model.
        Expects a list of sample input dictionaries, which should have the same format as the data used in `.predict`
        method for the mlmodel. More specifically, the input name need to be specified in the data, unless it's a single
        input model where the name will be auto inferred.

    calibration_op_group_size: int
        While running inference during calibration, only have `calibration_op_group_size` of intermediate outputs
        appended to outputs at a time. If the model is very large, it could lead to the temperary model having
        thousands of outputs, which may lead to model hanging forever during model loading. To work around this
        issue, intermediate outputs are grouped into smaller groups, where each time a temperary model will only
        have `calibration_op_group_size` outputs. By default (op_group_size = -1), op_group_size is equal to the
        number of valid intermediate ops.

    Returns
    -------
    model: MLModel
        The activation quantized MLModel instance.

    Examples
    --------
    .. sourcecode:: python

        import coremltools as ct
        import coremltools.optimize as cto

        model = ct.coreml.models.MLModel("my_model.mlpackage")
        activation_config = cto.coreml.OptimizationConfig(
            global_config=cto.coreml.OpLinearQuantizerConfig(mode="linear_symmetric")
        )
        compressed_model_a8 = cto.coreml.linear_quantize_activations(
            model, activation_config, sample_data
        )

        # (Optional) It's recommended to use with linear_quantize_weights.
        weight_config = cto.coreml.OptimizationConfig(
            global_config=cto.coreml.OpLinearQuantizerConfig(mode="linear_symmetric")
        )
        compressed_model_w8a8 = cto.coreml.linear_quantize_weights(compressed_model_a8, weight_config)
    
coremltools/optimize/coreml/_post_training_quantization.py