def iterate_and_apply_convert(
    args: Any,
    quant_infos: List[Optional[Tuple[float, int, torch.dtype]]],
    quant_or_dequant_needed: List[bool],
    op: Callable,
    flattened_tensor_infos_idx=None,
) -> Any:
    """
    Inputs:
      `args`: arguments to a function, may contain nested types, for example:
        ([torch.Tensor, torch.Tensor], int, (int, int))
      `quant_infos`: tensor information containers for each tensor
        in `args`, flattened, for example corresponding with above:
        ({...}, {...}, None, None, None)
       `quant_or_dequant_needed`: tensor information about whether do quantization
        containers for each tensorin `args`,
      `op`: cur quantizable op
    Returns `new_args`, where each tensor has been transformed by `func`.
    """

    if flattened_tensor_infos_idx is None:
        flattened_tensor_infos_idx = [0]
    if isinstance(args, tuple):
        new_args = []
        for arg in args:
            new_arg = iterate_and_apply_convert(
                arg,
                quant_infos,
                quant_or_dequant_needed,
                op,
                flattened_tensor_infos_idx,
            )
            new_args.append(new_arg)
        return tuple(new_args)
    elif isinstance(args, list):
        new_args = []
        for arg in args:
            new_arg = iterate_and_apply_convert(
                arg,
                quant_infos,
                quant_or_dequant_needed,
                op,
                flattened_tensor_infos_idx,
            )
            new_args.append(new_arg)
        return new_args
    else:
        # individual element
        cur_quant_infos = quant_infos[flattened_tensor_infos_idx[0]]
        cur_quant_or_dequant_needed = quant_or_dequant_needed[
            flattened_tensor_infos_idx[0]
        ]
        if (
            cur_quant_infos is not None
            and cur_quant_or_dequant_needed
            and isinstance(args, torch.Tensor)
        ):
            scale, zp, dtype = cur_quant_infos
            # For F.Linear, F.conv, the weight's may use per_channel.
            if (
                str(op) in conv_linear_ops
                and get_weight_arg_idx(str(op)) == flattened_tensor_infos_idx[0]
                and isinstance(scale, torch.Tensor)
                and scale.numel() > 1
            ):
                ch_axis = 0
                # conv_transpose's weight is iohw or iodhw
                if str(op) in [
                    str(F.conv_transpose2d),
                    str(torch.conv_transpose2d),
                    str(F.conv_transpose3d),
                    str(torch.conv_transpose3d),
                ]:
                    ch_axis = 1
                if (
                    torch.is_autocast_enabled("cpu")
                    and torch.get_autocast_dtype("cpu") == torch.bfloat16
                ):
                    # do autocast in Python side
                    if args.dtype == torch.float32:
                        args = args.to(dtype=torch.float32)
                    args = torch.quantize_per_channel(args, scale, zp, ch_axis, dtype)
                    args = args.dequantize()
                    args = args.to(dtype=torch.bfloat16)
                else:
                    args = torch.quantize_per_channel(args, scale, zp, ch_axis, dtype)
                    args = args.dequantize()
            else:
                # white list, conv, linear, matmul, we always convert it's input to bflat16 firstly, and then inser q+dq
                if (
                    str(op)
                    in conv_linear_ops
                    + [
                        str(torch.matmul),
                        str(torch.Tensor.matmul),
                        str(torch.bmm),
                        str(torch.Tensor.bmm),
                    ]
                    + embedding_op
                    or str(type(op)) in conv_linear_modules
                ):
                    if (
                        torch.is_autocast_enabled("cpu")
                        and torch.get_autocast_dtype("cpu") == torch.bfloat16
                    ):
                        if args.dtype == torch.bfloat16:
                            args = args.to(dtype=torch.float32)
                        args = torch.quantize_per_tensor(
                            args, scale.item(), zp.item(), dtype
                        )
                        args = args.dequantize()
                        args = args.to(dtype=torch.bfloat16)
                    else:
                        args = torch.quantize_per_tensor(
                            args, scale.item(), zp.item(), dtype
                        )
                        args = args.dequantize()
                else:
                    # fall through
                    args_is_bfloat16 = False
                    if args.dtype == torch.bfloat16:
                        args_is_bfloat16 = True
                        args = args.to(dtype=torch.float32)
                    args = torch.quantize_per_tensor(
                        args, scale.item(), zp.item(), dtype
                    )
                    args = args.dequantize()
                    if args_is_bfloat16:
                        args = args.to(dtype=torch.bfloat16)
        flattened_tensor_infos_idx[0] += 1
        return args

    Inputs:
      `args`: arguments to a function, may contain nested types, for example:
        ([torch.Tensor, torch.Tensor], int, (int, int))
      `quant_infos`: tensor information containers for each tensor
        in `args`, flattened, for example corresponding with above:
        ({...}, {...}, None, None, None)
       `quant_or_dequant_needed`: tensor information about whether do quantization
        containers for each tensorin `args`,
      `op`: cur quantizable op
    Returns `new_args`, where each tensor has been transformed by `func`.
    
intel_extension_for_pytorch/quantization/_quantization_state_utils.py