def _extract_inputs_from_exir_program(
    exported_program,  # torch.export.ExportedProgram
) -> Tuple[List[TensorType], Dict[str, torch.Tensor], Dict[str, torch.Tensor], Dict[str, str],]:
    """
    Extract "input"s from torch.export.ExportedProgram

    For easy delegation to different backends,
    EXIR lifts constants and buffer references also as inputs,
    so we will extract all user inputs and constants and mutable buffers

    EXIR has 2 types of constants:
    1. parameters (e.g. weight of torch.nn.Linear)
    2. constants (e.g. torch.tensor([0]) inside a torch.nn.Module)

    We consider buffers that are not mutated also as constant,
    e.g. batch norm running mean and variance are constant during inference

    Given:
        exported_program: torch.export.ExportedProgram
    Return:
        user_inputs: List[ct.TensorType]
            list of coremltools input tensor specifications
        constants: Dict[str, torch.Tensor]
            dictionary mapping variable names to torch constant tensors
        buffers: Dict[str, torch.Tensor]
            dictionary mapping torch mutable buffer names to tensors
        input_name_to_source_buffer_name: Dict[str, str]
            dictionary mapping input variable names to underlying mutable buffer names,
            i.e. these input variables are "read" from mutable buffer

    PS: Here is an example of buffers and input_name_to_source_buffer_name. Consider a toy model
    ```
    class Model(torch.nn.Module):
        def __init__(self):
            super().__init__()
            self.register_buffer("state", torch.tensor([7, 5, 6], dtype=torch.float16))

        def forward(self, x):
            ...
    ```
    The EXIR program is
    ```
    class GraphModule(torch.nn.Module):
        def forward(self, arg0_1: "f16[3]", arg1_1: "f16[3]"):
            ...

    Graph signature: ExportGraphSignature(input_specs=[InputSpec(kind=<InputKind.BUFFER: 3>, arg=TensorArgument(name='arg0_1'), target='state', persistent=True), ...
    ```
    We will have
    ```
    buffers = {"state": torch.tensor([7, 5, 6], dtype=torch.float16)}
    input_name_to_source_buffer_name = {"arg0_1": "state"}
    ```
    """
    # prepare placeholder nodes and parameters and buffers into convenient dicts
    placeholders = _extract_placeholders_from_exir_program(exported_program)
    parameters = _extract_parameters_from_exir_program(exported_program)
    buffers = _extract_buffers_from_exir_program(exported_program)

    # loop over input specs and populate results
    torch_user_inputs = {}
    constants = {}
    input_name_to_source_buffer_name = {}
    for input_spec in exported_program.graph_signature.input_specs:
        if input_spec.kind == torch.export.graph_signature.InputKind.USER_INPUT:
            node = placeholders[input_spec.arg.name]
            if node.meta is None or "val" not in node.meta:
                raise ValueError(
                    "Placeholder torch.fx.Node metadata val is required in Core ML conversion"
                )
            val = node.meta["val"]
            if not isinstance(val, torch.Tensor):
                raise NotImplementedError(
                    "Placeholder val must be a tensor or fake tensor, "
                    f"but got type {type(val)}, value {str(val)}"
                )
            if val.dim_order() != tuple(range(val.dim())):
                # TODO (rdar://139251491) Core ML can support a special case:
                # rank-4 (0, 2, 3, 1) dim order, i.e. ct.ImageType(channel_first=False)
                raise NotImplementedError(
                    "Core ML does not have general support for non-contiguous dim order"
                )
            torch_user_inputs[node.name] = val

        elif input_spec.kind == torch.export.graph_signature.InputKind.PARAMETER:
            constants[input_spec.arg.name] = parameters[input_spec.target]

        elif input_spec.kind == torch.export.graph_signature.InputKind.CONSTANT_TENSOR:
            constants[input_spec.arg.name] = exported_program.constants[input_spec.target]

        elif input_spec.kind == torch.export.graph_signature.InputKind.BUFFER:
            if input_spec.target in exported_program.graph_signature.buffers_to_mutate.values():
                input_name_to_source_buffer_name[input_spec.arg.name] = input_spec.target
            else:
                constants[input_spec.arg.name] = buffers.pop(input_spec.target)

        else:
            raise NotImplementedError(
                "Only 4 types of inputs handled yet: user input, parameter, constant, buffer. "
                f"But got {input_spec.kind}"
            )

    ct_input_types = _construct_ct_input_types_from_torch_user_inputs(
        exported_program, torch_user_inputs
    )

    return ct_input_types, constants, buffers, input_name_to_source_buffer_name

    Extract "input"s from torch.export.ExportedProgram

    For easy delegation to different backends,
    EXIR lifts constants and buffer references also as inputs,
    so we will extract all user inputs and constants and mutable buffers

    EXIR has 2 types of constants:
    1. parameters (e.g. weight of torch.nn.Linear)
    2. constants (e.g. torch.tensor([0]) inside a torch.nn.Module)

    We consider buffers that are not mutated also as constant,
    e.g. batch norm running mean and variance are constant during inference

    Given:
        exported_program: torch.export.ExportedProgram
    Return:
        user_inputs: List[ct.TensorType]
            list of coremltools input tensor specifications
        constants: Dict[str, torch.Tensor]
            dictionary mapping variable names to torch constant tensors
        buffers: Dict[str, torch.Tensor]
            dictionary mapping torch mutable buffer names to tensors
        input_name_to_source_buffer_name: Dict[str, str]
            dictionary mapping input variable names to underlying mutable buffer names,
            i.e. these input variables are "read" from mutable buffer

    PS: Here is an example of buffers and input_name_to_source_buffer_name. Consider a toy model
    ```
    class Model(torch.nn.Module):
        def __init__(self):
            super().__init__()
            self.register_buffer("state", torch.tensor([7, 5, 6], dtype=torch.float16))

        def forward(self, x):
            ...
    ```
    The EXIR program is
    ```
    class GraphModule(torch.nn.Module):
        def forward(self, arg0_1: "f16[3]", arg1_1: "f16[3]"):
            ...

    Graph signature: ExportGraphSignature(input_specs=[InputSpec(kind=<InputKind.BUFFER: 3>, arg=TensorArgument(name='arg0_1'), target='state', persistent=True), ...
    ```
    We will have
    ```
    buffers = {"state": torch.tensor([7, 5, 6], dtype=torch.float16)}
    input_name_to_source_buffer_name = {"arg0_1": "state"}
    ```
    
coremltools/converters/mil/frontend/torch/exir_utils.py