def op_prepare_before_hook(
        self,
        op: Callable,
        args: Tuple[Any, ...],
        kwargs: Dict[str, Any],
    ) -> Tuple[Tuple[Any, ...], Dict[str, Any]]:
        """
        This function is expected to be called on args and kwargs of
        `op` directly before `op` is executed.
        We do the following:
        * pass the inputs through observers, if needed
        The function returns modified `args` and `kwargs`.
        """
        seen_q_op_info = self._get_cur_seen_q_op_info()

        def _maybe_observe(arg, tensor_info):
            tensor_id = tensor_info.id
            # TODO: do not run this twice on input and output
            if str(tensor_id) in self.tensor_id_to_observer:
                observer = self.tensor_id_to_observer[str(tensor_id)]
                if isinstance(arg, torch.Tensor) and arg.dtype != torch.float32:
                    dtype = arg.dtype
                    out = observer(arg.float())
                    return out.to(dtype)
                return observer(arg)
            else:
                return arg

        # If user changes observer's dtype and re-do calibration, we need to update
        # the tensor_info.inf_dtype  and force dtype with the new oberver's dtype.
        quantized_dtype = [torch.quint8, torch.qint8]
        for i, tensor_info in enumerate(seen_q_op_info.input_tensor_infos):
            if (
                tensor_info is not None
                and str(tensor_info.id) in self.tensor_id_to_observer
            ):
                tensor_id = tensor_info.id
                observer = self.tensor_id_to_observer[str(tensor_id)]
                set_tensor_info_dtype(tensor_info, observer)
                force_dtype = seen_q_op_info.input_tensor_force_inf_dtype[i]
                if (
                    force_dtype in quantized_dtype
                    and force_dtype != tensor_info.orig_dtype
                    and force_dtype != observer.dtype
                ):
                    seen_q_op_info.input_tensor_force_inf_dtype[i] = observer.dtype
        args = iterate_and_apply(
            args, seen_q_op_info.input_tensor_infos, _maybe_observe
        )
        # works for nn.module case
        weight_tensor_info = seen_q_op_info.weight_tensor_infos
        for i, tensor_info in enumerate(weight_tensor_info):
            if tensor_info is None:
                continue
            tensor_id = tensor_info.id
            if (
                str(seen_q_op_info.idx) + "_" + str(tensor_id)
                in self.weight_tensor_id_to_observer
            ):
                observer = self.weight_tensor_id_to_observer[
                    str(seen_q_op_info.idx) + "_" + str(tensor_id)
                ]
                set_tensor_info_dtype(tensor_info, observer)
                # if has bias, the dim is 1, we don't need run observer for it.
                if isinstance(op, torch.nn.LSTM):
                    if op._flat_weights[i].dim() > 1:
                        observer(op._flat_weights[i])
                    else:
                        pass
                elif isinstance(op, MergedEmbeddingBagWithCat):
                    observer(op.weights[i])
                else:
                    observer(op.weight)

        return args, kwargs

        This function is expected to be called on args and kwargs of
        `op` directly before `op` is executed.
        We do the following:
        * pass the inputs through observers, if needed
        The function returns modified `args` and `kwargs`.
        
intel_extension_for_pytorch/quantization/_quantization_state.py