def process_inplace_op(self, node: InternalTorchIRNode) -> None:
        """
        This utility:

        1. adds ``mb.coreml_update_state`` after each torch inplace ops.
        2. adjusts the dtype across state / tensor.

        In torch, inplaces ops have the following properties:

        1. op type has the suffix of ``_``. For instance, ``add_``, ``mul_``, etc.
        2. The op does an inplace update for the first input tensor.

        For instance, the following syntax of a TorchScript:

            %3 = add_(%1, %2)

        denotes an inplace ``add`` operation on the ``%1`` tensor. The memory buffer
        of ``%1`` is updated and returned as a reference ``%3``.

        Here are the steps what this utility does, lets use the above
        simple torch script as an example, after adding the ``add_`` in the context,
        we currently have a MIL graph as ``%3 = add(x=%1, y=%2)``:

        1. Validate the first input (``%1``) comes from a state source by checking if the tensor's name ``1`` is in ``name_to_source_state``. If not, this utility does nothing.
        2. Say ``name_to_source_state["1"] = %state``. ``%state, %3`` can potentially has different dtype.
           For instance, the user could specify ``%state`` in fp16, while
           the MIL program in the front end conversion stage is
           still in fp32. Hence we cast ``%3`` into ``%state``'s dtype:

           (%state: fp16) -> {
                ...
                %3_<fp32> = add(x=%1, y=%2)
                %3_cast<fp16> = cast(x=%3_, dtype="fp16")
            }
        3. Insert a ``coreml_update_state`` and cast the output back to ``%3``'s original dtype:

           (%state: fp16) -> {
                ...
                %3_<fp32> = add(x=%1, y=%2)
                %3_cast<fp16> = cast(x=%3_, dtype="fp16")
                %3_update<fp16> = coreml_update_state(state=%state, value=%3_cast)
                %3<fp32> = cast(x=%3_update, dtype="fp32")
            }
        4. Set ``name_to_source_state["3"] = %state``, so the state chain can be used in the downstream.

        The below Torch Script model,

            (%state: fp16) -> {
                ...
                %3 = add_(%1, %2)
                %out = sub_(%3, %4)
            }

        will result in:

           (%state: fp16) -> {
                %1_<fp16> = read_state(%state)
                %1<fp32> = cast(x=%1_, dtype="fp32")
                %3_<fp32> = add(x=%1, y=%2)
                %3_cast<fp16> = cast(x=%3_, dtype="fp16")
                %3_update<fp16> = coreml_update_state(state=%state, value=%3_cast)
                %3<fp32> = cast(x=%3_update, dtype="fp32")
                %out_<fp32> = sub(x=%3, y=%4)
                %out_cast<fp16> = cast(x=%out_, dtype="fp16")
                %out_update<fp16> = coreml_update_state(state=%state, value=%out_cast)
                %out<fp32> = cast(x=%out_update, dtype="fp32")
            }

        Please note that, the intermediate ``cast`` ops would be removed
        by the ``add_fp16_cast`` + ``cast_optimization`` graph passes:

           (%state: fp16) -> {
                %1<fp16> = read_state(%state)
                %3_<fp16> = add(x=%1, y=%2)
                %3<fp16> = coreml_update_state(state=%state, value=%3_)
                %out_<fp16> = sub(x=%3, y=%4)
                %out<fp16> = coreml_update_state(state=%state, value=%out_)
            }

        """
        assert self.frontend == TorchFrontend.TORCHSCRIPT, "Only torch script has no in-place op"

        if len(node.inputs) == 0:
            return

        if node.inputs[0] not in self.name_to_source_state:
            return

        source_state = self.name_to_source_state[node.inputs[0]]
        self.name_to_source_state[node.name] = source_state
        value_node = self[node.name]
        cast_value = mb.cast(x=value_node, dtype=builtin_to_string(source_state.dtype))
        update = mb.coreml_update_state(state=source_state, value=cast_value)
        cast_update = mb.cast(x=update, dtype=builtin_to_string(value_node.dtype), name=node.name)
        self.add(cast_update, torch_name=node.name, override=True)

        This utility:

        1. adds ``mb.coreml_update_state`` after each torch inplace ops.
        2. adjusts the dtype across state / tensor.

        In torch, inplaces ops have the following properties:

        1. op type has the suffix of ``_``. For instance, ``add_``, ``mul_``, etc.
        2. The op does an inplace update for the first input tensor.

        For instance, the following syntax of a TorchScript:

            %3 = add_(%1, %2)

        denotes an inplace ``add`` operation on the ``%1`` tensor. The memory buffer
        of ``%1`` is updated and returned as a reference ``%3``.

        Here are the steps what this utility does, lets use the above
        simple torch script as an example, after adding the ``add_`` in the context,
        we currently have a MIL graph as ``%3 = add(x=%1, y=%2)``:

        1. Validate the first input (``%1``) comes from a state source by checking if the tensor's name ``1`` is in ``name_to_source_state``. If not, this utility does nothing.
        2. Say ``name_to_source_state["1"] = %state``. ``%state, %3`` can potentially has different dtype.
           For instance, the user could specify ``%state`` in fp16, while
           the MIL program in the front end conversion stage is
           still in fp32. Hence we cast ``%3`` into ``%state``'s dtype:

           (%state: fp16) -> {
                ...
                %3_<fp32> = add(x=%1, y=%2)
                %3_cast<fp16> = cast(x=%3_, dtype="fp16")
            }
        3. Insert a ``coreml_update_state`` and cast the output back to ``%3``'s original dtype:

           (%state: fp16) -> {
                ...
                %3_<fp32> = add(x=%1, y=%2)
                %3_cast<fp16> = cast(x=%3_, dtype="fp16")
                %3_update<fp16> = coreml_update_state(state=%state, value=%3_cast)
                %3<fp32> = cast(x=%3_update, dtype="fp32")
            }
        4. Set ``name_to_source_state["3"] = %state``, so the state chain can be used in the downstream.

        The below Torch Script model,

            (%state: fp16) -> {
                ...
                %3 = add_(%1, %2)
                %out = sub_(%3, %4)
            }

        will result in:

           (%state: fp16) -> {
                %1_<fp16> = read_state(%state)
                %1<fp32> = cast(x=%1_, dtype="fp32")
                %3_<fp32> = add(x=%1, y=%2)
                %3_cast<fp16> = cast(x=%3_, dtype="fp16")
                %3_update<fp16> = coreml_update_state(state=%state, value=%3_cast)
                %3<fp32> = cast(x=%3_update, dtype="fp32")
                %out_<fp32> = sub(x=%3, y=%4)
                %out_cast<fp16> = cast(x=%out_, dtype="fp16")
                %out_update<fp16> = coreml_update_state(state=%state, value=%out_cast)
                %out<fp32> = cast(x=%out_update, dtype="fp32")
            }

        Please note that, the intermediate ``cast`` ops would be removed
        by the ``add_fp16_cast`` + ``cast_optimization`` graph passes:

           (%state: fp16) -> {
                %1<fp16> = read_state(%state)
                %3_<fp16> = add(x=%1, y=%2)
                %3<fp16> = coreml_update_state(state=%state, value=%3_)
                %out_<fp16> = sub(x=%3, y=%4)
                %out<fp16> = coreml_update_state(state=%state, value=%out_)
            }

        
coremltools/converters/mil/frontend/torch/converter.py