def _get_activation_calibration_stats(
    fpmodel: "ct.models.MLModel",
    sample_data: List[Dict[str, np.ndarray]],
    calibration_op_group_size: int = -1,
) -> Dict[str, Dict[str, float]]:
    """
    Calibration and store a dict of intermediate tensor stats.
    E.g. activation_stats_dict = {tensor_0: {rmin: 0.2, rmax: 3.8}, tensor_1: {rmin: 4.5, rmax: 12.6}}}
    Parameters
    ----------
    fpmodel: MLModel
        Path to fp16/fp32 "model.mlpackage". (Expect the orginal mlmodel, not the one with quantize and dequant op)
    sample_data: list[dict]
        Data for calibration.
    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
    -------
    activation_calibration_stats: dict
    """
    debugger = ModelDebugger(fpmodel)
    activation_stats_dict = defaultdict(dict)
    intermediate_output_names = debugger.get_intermediate_output_names(
        lambda op: (op.spec.type != "const")
    )

    # Get data ranges for all inputs.
    for data in sample_data:
        for input_name in data:
            _update_tensor_range(input_name, data[input_name], activation_stats_dict)

    # The last few elements in intermediate_output_names might be output.
    # We don't maintain min/max value for an output tensor.
    # If it's an output tensor we exclude it, otherwise include it.
    model_spec = fpmodel.get_spec()
    output_count = len(fpmodel.get_spec().description.output)
    output_names = []
    for i in range(0, output_count):
        output_name = model_spec.description.output[i].name
        output_names.append(output_name)

    for intermediate_output_name in intermediate_output_names:
        if intermediate_output_name in output_names:
            intermediate_output_names.remove(intermediate_output_name)

    # Get data ranges for all intermeditate outputs.
    for data in tqdm(
        sample_data,
        desc="Running compression pass linear_quantize_activations",
        unit=" calibration samples",
    ):
        debugger.step(
            inputs=data,
            activation_stats_dict=activation_stats_dict,
            intermediate_output_names=intermediate_output_names,
            op_group_size=calibration_op_group_size,
        )

    # Handle a special case - concat ops.
    _adjust_concat_surrounding_activation_stats(
        debugger._get_concat_op_info(), activation_stats_dict
    )

    return activation_stats_dict

    Calibration and store a dict of intermediate tensor stats.
    E.g. activation_stats_dict = {tensor_0: {rmin: 0.2, rmax: 3.8}, tensor_1: {rmin: 4.5, rmax: 12.6}}}
    Parameters
    ----------
    fpmodel: MLModel
        Path to fp16/fp32 "model.mlpackage". (Expect the orginal mlmodel, not the one with quantize and dequant op)
    sample_data: list[dict]
        Data for calibration.
    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
    -------
    activation_calibration_stats: dict
    
coremltools/optimize/coreml/experimental/_post_training_quantization.py