def save_multifunction(
    desc: MultiFunctionDescriptor,
    destination_path: str,
):
    """
    Save a :py:class:`MultiFunctionDescriptor` instance into a multifunction ``mlpackage``.
    This function also performs constant deduplication across functions to allow for weight sharing.

    Parameters
    ----------
    desc: MultiFunctionDescriptor
        Multifunction descriptor to save on the disk.

    destination_path: str
        The path where the new ``mlpackage`` will be saved.

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

        from coremltools.utils import MultiFunctionDescriptor, save_multifunction

        desc = MultiFunctionDescriptor("my_model_1.mlpackage")
        desc.add_function("my_model_2.mlpackage", "main", "main_2")
        desc.default_function_name = "main_2"

        save_multifunction(desc, "multifunction_model.mlpackage")

    See Also
    --------
    MultiFunctionDescriptor

    """
    def get_function_spec(
        spec: "_proto.Model_pb2", func_name: str
    ) -> _proto.Model_pb2.FunctionDescription:
        """
        Utils to construct a FunctionDescription from the source spec.
        """
        model_desc = spec.description
        # For single function model, we construct the FunctionDescription ourselves
        if len(model_desc.functions) == 0:
            assert func_name == "main", f"invalid function name {func_name}"
            return _proto.Model_pb2.FunctionDescription(
                input=model_desc.input,
                output=model_desc.output,
                state=model_desc.state,
                predictedFeatureName=model_desc.predictedFeatureName,
                predictedProbabilitiesName=model_desc.predictedProbabilitiesName,
            )
        # For multifunction model, we look for the corresponding FunctionDescription
        for func_desc in model_desc.functions:
            if func_desc.name != func_name:
                continue
            res = _proto.Model_pb2.FunctionDescription()
            res.CopyFrom(func_desc)
            res.name = ""
            return res

    # compile model information: spec / weight_dir
    modelpath_to_spec_and_weightdir = {}
    for k, v in desc._name_to_source_function.items():
        model_path = v[0]
        if model_path in modelpath_to_spec_and_weightdir:
            continue
        spec = desc._modelpath_to_spec[model_path]
        weight_dir = _try_get_weights_dir_path(model_path)
        if weight_dir is None:
            raise ValueError(f"weight_dir for model_path {model_path} not found.")
        modelpath_to_spec_and_weightdir[model_path] = (spec, weight_dir)

    # min spec version to support multi-functions model is iOS18
    # we also make the target spec version the max among the input models
    spec_version = max(
        map(lambda val: val[0].specificationVersion, modelpath_to_spec_and_weightdir.values())
    )
    spec_version = max(spec_version, _SPECIFICATION_VERSION_IOS_18)

    # convert spec into pymil program
    modelpath_to_pymil = {}
    for model_path, (spec, weight_dir) in modelpath_to_spec_and_weightdir.items():
        prog = _milproto_to_pymil.load(
            spec,
            spec_version,
            weight_dir,
        )
        modelpath_to_pymil[model_path] = prog

    # construct a multifunction pymil program
    multifunction_prog = _mil.Program()
    function_to_desc = {}
    for target_func_name, v in desc._name_to_source_function.items():
        model_path = v[0]
        src_func_name = v[1]
        prog = modelpath_to_pymil[model_path]
        _ct.utils._multifunction_program_append_unifunction_program(
            multifunction_prog, prog, src_func_name, target_func_name
        )

        # get the corresponding function description from the spec
        spec = modelpath_to_spec_and_weightdir[model_path][0]
        function_spec = get_function_spec(spec, src_func_name)
        assert function_spec.name == "", "function_spec should not have name set"
        function_spec.name = target_func_name
        function_to_desc[target_func_name] = function_spec

    # Here we deduplicate the same weights across functions, to allow consts to use
    # the same blob file value when lowered into milproto.
    # By weight sharing, we can make the model size as small as we could.
    graph_pass = _PASS_REGISTRY["common::const_deduplication"]
    graph_pass._deduplicate_const_across_functions(multifunction_prog)

    # set default function name
    default_function_name = desc.default_function_name
    if default_function_name is None:
        raise ValueError(
            "default_function_name must be set for the MultiFunctionDescriptor instance before calling save_multifunction."
        )

    if default_function_name not in multifunction_prog.functions:
        raise ValueError(
            f"default_function_name {default_function_name} not found in the program. Available functions names are {list(multifunction_prog.functions.keys())}"
        )
    multifunction_prog.default_function_name = default_function_name

    # export program into multi-functions CoreML model
    functions = []
    for func in multifunction_prog.functions:
        functions.append(function_to_desc[func])
    model_description = _proto.Model_pb2.ModelDescription(
        functions=functions,
        defaultFunctionName=default_function_name,
    )
    multifunction_prog.skip_all_passes = True
    multifunction_prog.export_as_multifunction = True
    mlmodel = _mil_convert(
        multifunction_prog,
        convert_to="mlprogram",
        convert_from="milinternal",
        specification_version=spec_version,
        compute_units=_ct.ComputeUnit.CPU_ONLY,
        model_description=model_description,
        skip_model_load=True,
    )
    mlmodel.save(destination_path)

    Save a :py:class:`MultiFunctionDescriptor` instance into a multifunction ``mlpackage``.
    This function also performs constant deduplication across functions to allow for weight sharing.

    Parameters
    ----------
    desc: MultiFunctionDescriptor
        Multifunction descriptor to save on the disk.

    destination_path: str
        The path where the new ``mlpackage`` will be saved.

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

        from coremltools.utils import MultiFunctionDescriptor, save_multifunction

        desc = MultiFunctionDescriptor("my_model_1.mlpackage")
        desc.add_function("my_model_2.mlpackage", "main", "main_2")
        desc.default_function_name = "main_2"

        save_multifunction(desc, "multifunction_model.mlpackage")

    See Also
    --------
    MultiFunctionDescriptor

    
coremltools/models/utils.py