class_docstr: In the backend, for better performance, quantized op can have 1 input scale
  fused within the quantized op kernel. For binary ops, there are 2 inputs,
  but only 1 can get fused. For example, for quantized ``add``
  
  .. code-block::
  
      MIL graph (consists of MIL ops):
  
          dequantize(x, s_x, zp_x) -|
          x_fp = (x - zp_x) * s_x   |
                                    |->  add(x_fp, y_fp)   -> quantize(z_fp, s_z, zp_z)
          dequantize(y, s_y, zp_y) -|   z_fp = x_fp + y_fp      z = z_fp / s_z + zp_z
          y_fp = (y - zp_y) * s_y
  
      Backend graph (consists of backend instructions, usually including + - * / and fused *+):
  
          x_shift = x - zp_x -------------------------|
                                                      |-> z_fp = s_x * x_shift + y_fp -> z = z_fp / s_z + zp_z
          y_shift = y - zp_y -> y_fp = s_y * y_shift -|
  
  Where ``x`` and ``y`` are the inputs, ``z`` is the output,
  ``s`` and ``zp`` are the corresponding scale and zero point.
  
  The reason why fusing one scale leads to better performance is,
  instead of 2 instructions ``x_fp = s_x * x_shift`` and ``z_fp = x_fp + y_fp``,
  a single ``z_fp = x_shift * s_x + y_fp`` instruction achieves the same result.
  
  In this pass, we normalize ``s_y`` to 1, so the ``y_fp = s_y * y_shift``
  instruction can get skipped as well, leading to even better performance.
  This pass only applies to distributive binary ops such as ``add`` and ``sub``
  
  Appendix: Mathematical and Computer-Scientific Details
  
  Mathematically, for a binary operator ``.op.``
  
  .. code-block::
  
      z_fp = (x - zp_x) * s_x .op. (y - zp_y) * s_y
           = s_y * [(x - zp_x) * s_x/s_y .op. (y - zp_y) * 1]
  
  The corresponding pseudo code is
  
  .. code-block::
  
      # before
      z_fp = (x - zp_x) * s_x .op. (y - zp_y) * s_y
      z = z_fp / s - zp_z
  
      # after
      z_fp_modified = (x - zp_x) * s_x/s_y .op. (y - zp_y) * 1.0
      z = z_fp_modified / (s_z/s_y) - zp_z
  
  Concretely, as a MIL graph pass
  
  .. code-block::
  
      Input graph:
          dequantize(scale=s_x) -|
                                 |-> op -> quantize(scale=s_z)
          dequantize(scale=s_y) -|
  
      Output graph:
          dequantize(scale=s_x/s_y) -|
                                     |-> op -> quantize(scale=s_z/s_y)
          dequantize(scale=1.0)     -|
  
  PS: we only support scalar ``s_y`` for now. If ``s_y`` is not scalar but
  ``s_x`` is, we would swap ``x`` and ``y``. Support for both-vector case is
  to be explored, due to the broadcasting complication.
class_docstr_tok: ["In","the","backend",",","for","better","performance",",","quantized","op","can","have","1","input","scale","fused","within","the","quantized","op","kernel",".","For","binary","ops",",","there","are","2","inputs",",","but","only","1","can","get","fused",".","For","example",",","for","quantized","``","add","``","..","code-block",":",":","MIL","graph","(","consists","of","MIL","ops",")",":","dequantize","(","x",",","s_x",",","zp_x",")","-|","x_fp","=","(","x","-","zp_x",")","*","s_x","|","|-",">","add","(","x_fp",",","y_fp",")","-",">","quantize","(","z_fp",",","s_z",",","zp_z",")","dequantize","(","y",",","s_y",",","zp_y",")","-|","z_fp","=","x_fp","+","y_fp","z","=","z_fp","\/","s_z","+","zp_z","y_fp","=","(","y","-","zp_y",")","*","s_y","Backend","graph","(","consists","of","backend","instructions",",","usually","including","+","-","*","\/","and","fused","*","+",")",":","x_shift","=","x","-","zp_x","--","--","--","--","--","--","--","--","--","--","--","--","-|","|-",">","z_fp","=","s_x","*","x_shift","+","y_fp","-",">","z","=","z_fp","\/","s_z","+","zp_z","y_shift","=","y","-","zp_y","-",">","y_fp","=","s_y","*","y_shift","-|","Where","``","x","``","and","``","y","``","are","the","inputs",",","``","z","``","is","the","output",",","``","s","``","and","``","zp","``","are","the","corresponding","scale","and","zero","point",".","The","reason","why","fusing","one","scale","leads","to","better","performance","is",",","instead","of","2","instructions","``","x_fp","=","s_x","*","x_shift","``","and","``","z_fp","=","x_fp","+","y_fp","``",",","a","single","``","z_fp","=","x_shift","*","s_x","+","y_fp","``","instruction","achieves","the","same","result",".","In","this","pass",",","we","normalize","``","s_y","``","to","1",",","so","the","``","y_fp","=","s_y","*","y_shift","``","instruction","can","get","skipped","as","well",",","leading","to","even","better","performance",".","This","pass","only","applies","to","distributive","binary","ops","such","as","``","add","``","and","``","sub","``","Appendix",":","Mathematical","and","Computer-Scientific","Details","Mathematically",",","for","a","binary","operator","``",".op.","``","..","code-block",":",":","z_fp","=","(","x","-","zp_x",")","*","s_x",".op",".","(","y","-","zp_y",")","*","s_y","=","s_y","*","[","(","x","-","zp_x",")","*","s_x\/s_y",".op",".","(","y","-","zp_y",")","*","1","]","The","corresponding","pseudo","code","is","..","code-block",":",":","#","before","z_fp","=","(","x","-","zp_x",")","*","s_x",".op",".","(","y","-","zp_y",")","*","s_y","z","=","z_fp","\/","s","-","zp_z","#","after","z_fp_modified","=","(","x","-","zp_x",")","*","s_x\/s_y",".op",".","(","y","-","zp_y",")","*","1.0","z","=","z_fp_modified","\/","(","s_z\/s_y",")","-","zp_z","Concretely",",","as","a","MIL","graph","pass","..","code-block",":",":","Input","graph",":","dequantize","(","scale=s_x",")","-|","|-",">","op","-",">","quantize","(","scale=s_z",")","dequantize","(","scale=s_y",")","-|","Output","graph",":","dequantize","(","scale=s_x\/s_y",")","-|","|-",">","op","-",">","quantize","(","scale=s_z\/s_y",")","dequantize","(","scale=1.0",")","-|","PS",":","we","only","support","scalar","``","s_y","``","for","now",".","If","``","s_y","``","is","not","scalar","but","``","s_x","``","is",",","we","would","swap","``","x","``","and","``","y","``",".","Support","for","both-vector","case","is","to","be","explored",",","due","to","the","broadcasting","complication","."]
docstr: try to match distributive quantized binary op:
          ...
           ^
           |
      dequantize(x) -|
                     |-> op(x, y) (-> relu) -> quantize(z)
      dequantize(y) -|
           |
           v
          ...
  
  return dequantize_x, dequantize_y, quantize_z for further transformation
  
  return None if no match
docstr_tok: ["try","to","match","distributive","quantized","binary","op",":","...","^","|","dequantize","(","x",")","-|","|-",">","op","(","x",",","y",")","(","-",">","relu",")","-",">","quantize","(","z",")","dequantize","(","y",")","-|","|","v","...","return","dequantize_x",",","dequantize_y",",","quantize_z","for","further","transformation","return","None","if","no","match"]
code: def match_pattern(
      self, op: Operation
  ) -> Tuple[Operation, Operation, Operation]:
      """
      try to match distributive quantized binary op:
              ...
               ^
               |
          dequantize(x) -|
                         |-> op(x, y) (-> relu) -> quantize(z)
          dequantize(y) -|
               |
               v
              ...
  
      return dequantize_x, dequantize_y, quantize_z for further transformation
  
      return None if no match
      """
      # make sure the op is distributive
      if op.op_type not in self.DISTRIBUTIVE_BINARY_OPS:
          return None
  
      # quantized op may be fused with relu
      # relu would not affect distributivity
      tail_op = op
      if _check_child_op_type(op, "relu"):
          tail_op = op.outputs[0].child_ops[0]
  
      # make sure the inputs are quantized
      dequantize_x = op.x.op
      dequantize_y = op.y.op
      if (
          dequantize_x is None
          or dequantize_y is None
          or dequantize_x.op_type != "dequantize"
          or dequantize_y.op_type != "dequantize"
      ):
          return None
  
      # make sure the output is quantized
      if not _check_child_op_type(tail_op, "quantize"):
          return None
      quantize_z = tail_op.outputs[0].child_ops[0]
  
      # make sure the intermediate results are not block outputs
      # since we only guarantee conservation of z
      if not _check_no_output_connection(
          op.enclosing_block,
          [dequantize_x, dequantize_y, op, tail_op, quantize_z],
      ):
          return None
  
      return dequantize_x, dequantize_y, quantize_z
  
imports: from typing import Tuple
  import numpy
  import coremltools.converters.mil.mil.types
  from coremltools.converters.mil._deployment_compatibility import AvailableTarget
  from coremltools.converters.mil.mil import Block
  from coremltools.converters.mil.mil import Builder
  from coremltools.converters.mil.mil import Operation, Var
  from coremltools.converters.mil.mil.passes.graph_pass import AbstractGraphPass
  from coremltools.converters.mil.mil.passes.helper import _check_child_op_type, _check_no_output_connection, block_context_manager
  from coremltools.converters.mil.mil.passes.pass_registry import register_pass
label_desc: Use image node_id 2 for calling the distributive_quantized_binary_op_scale_normalization obj's underlying member method code with example usage: obj.match_pattern(op) and returns: dequantize_x,  dequantize_y,  quantize_z, None, None, None, None