code: def linear_quantize_weights(
      mlmodel: _MLModel, config: _OptimizationConfig
  ):
      """
      Utility function to convert a float precision MLModel of type ``mlprogram``, which uses
      float-precision weights, into a compressed MLModel that uses 8-bit weights. This is
      achieved by converting the float weight values that are stored in the ``const`` op
      into the ``constexpr_affine_dequantize`` op.
  
      This function uses linear quantization on the float weights, providing up to 2x
      savings in storage compared to float 16, or up to 4x savings compared to float 32.
      All computation at runtime uses float precision; the precision of the intermediate
      tensors and the compute precision of the ops are not altered.
  
      For each weight, this utility function converts the weight into the int8 or uint8 type using
      either `linear interpolation` (``"linear"`` mode) or `linear symmetric
      interpolation` (``"linear_symmetric"`` mode, the default).
  
      **Linear interpolation**
  
      Linear interpolation (``"linear"`` mode) maps the min/max of the float
      range to the 8-bit integer range ``[low, high]`` using a zero point (also called quantization bias, or
      offset) and a scale factor. For the int8 quantization, ``[low, high] = [-128, 127]``, while uint8
      quantization uses range ``[0, 255]``.
  
      ``"linear"`` mode uses the quantization formula:
  
      .. math::
         w_r = s * (w_q - z)
  
      Where:
  
          * :math:`w_r` and  :math:`s` are of type float.
          * :math:`w_r`` represents the float precision weight.
          * :math:`s` represents the scale.
          * :math:`w_q` and :math:`z` are of type 8-bit integer.
          * :math:`w_q` represents quantized weight.
          * :math:`z` represents the zero point.
  
      Quantized weights are computed as follows:
  
      .. math::
         w_q = cast\_to\_8\_bit\_integer(w_r / s + cast\_to\_float(z))
  
      Note: :math:`cast\_to\_8\_bit\_integer` is the process of clipping the input to range ``[low, high]`` followed by rounding and casting to 8-bit integer.
  
      In ``"linear"`` mode, ``s, z`` are computed by mapping the original float range
      ``[A, B]`` into the 8-bit integer range ``[-128, 127]`` or ``[0, 255]``. That is, you are solving the
      following linear equations:
  
          * ``B = s * (high - z)``
          * ``A = s * (low - z)``
  
      The equations result in the following:
  
          * ``s = (B - A) / (high - low)``
          * ``z = cast_to_8_bit_integer((low * B - high * A) / (B - A))``
  
      When the rank of weight ``w`` is 1, then ``s`` and ``z`` are both scalars. When the
      rank of the weight is greater than 1, then ``s`` and ``z`` are both vectors. In that
      case, scales are computed per `channel`, in which `channel` is the output dimension,
      which corresponds to the first dimension for ops such as ``conv`` and ``linear``, and
      the second dimension for the ``conv_transpose`` op.
  
      For ``"linear"`` mode, :math:`A = min(w_r)`, :math:`B = max(w_r)`.
  
      **Linear symmetric interpolation**
  
      With linear symmetric interpolation (``"linear_symmetric"`` mode, the default), rather than
      mapping the exact min/max of the float range to the quantized range, the function
      chooses the maximum absolute value between the min/max, which results in a
      floating-point range that is symmetric with respect to zero. This also makes the resulting zero
      point ``0`` for int8 weight and ``127`` for uint8 weight.
  
      For ``"linear_symmetric"`` mode:
  
         * :math:`A = -R` and :math:`B = R`, where :math:`R = max(abs(w_r))`.
         * This function maps to the range of ``[-127, 127]`` for int8 weight and ``[0, 254]`` for uint8 weight.
         * The result is ``s=(B-A)/254`` -> ``s=2R/254`` -> ``s=R/127``.
         * Solving for ``z``:
              * int8:  ``z = (-127 * R + 127 * R)/2R`` -> ``z=0``.
              * uint8: ``z = (0 * R + 254 * R)/2R`` -> ``z=127``.
  
      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 weight quantization.
  
      Returns
      -------
  
      model: MLModel
          The quantized MLModel instance.
  
      Examples
      --------
      .. sourcecode:: python
  
          import coremltools as ct
          import coremltools.optimize as cto
  
          model = ct.coreml.models.MLModel("my_model.mlpackage")
          config = cto.coreml.OptimizationConfig(
              global_config=cto.coreml.OpLinearQuantizerConfig(mode="linear_symmetric")
          )
          compressed_model = cto.coreml.linear_quantize_weights(model, config)
  
      """
  
      linear_weight_quantizer = _linear_quantize_weights(
          config, fake_compression=False
      )
      return _apply_graph_pass(mlmodel, linear_weight_quantizer)
  
imports: from collections import OrderedDict
  from typing import Callable, Dict, List, Optional
  import numpy
  from attrs import define, field, validators
  from tqdm import tqdm
  from coremltools import _SPECIFICATION_VERSION_IOS_16
  from coremltools.converters.mil.converter import mil_convert
  from coremltools.converters.mil.frontend.milproto import load
  from coremltools.converters.mil.mil.passes.defs.quantization import AbstractQuantizationPass
  from coremltools.models import MLModel
  from coremltools.optimize.coreml import OptimizationConfig
  from coremltools.optimize.coreml._config import _MetaDataDict
  from ._quantization_passes import WeightDecompressor
  from ._quantization_passes import linear_quantize_weights
  from ._quantization_passes import palettize_weights
  from ._quantization_passes import prune_weights
label_desc: Use image node_id 4 for calling a global function with example usage: linear_quantize_weights(mlmodel, config) and returns: _apply_graph_pass