docstr: This tests the transitivity of SharedQuantizationSpec, that is
  if A is shared with B, B is shared with C, then C should be shared with A as well
  
  x1 -> conv1 -> cat1 -----> cat2
  x2 -> conv2 -/            /
                 x3 -> add /
                 x4  /
  
  both cat has shared input and output, and because of cat and (cat1 -> cat2) is the same Tensor
  so there is an implicit sharing here, all tensors connect to cat1 and cat2 are in the same
  sharing group after transitive sharing
  
  the difference is that for this one, all edges and nodes are shared with the second input edge of cat
  instead of the first input edge of cat as in previous example
docstr_tok: ["This","tests","the","transitivity","of","SharedQuantizationSpec",",","that","is","if","A","is","shared","with","B",",","B","is","shared","with","C",",","then","C","should","be","shared","with","A","as","well","x1","-",">","conv1","-",">","cat1","--","--","-",">","cat2","x2","-",">","conv2","-\/","\/","x3","-",">","add","\/","x4","\/","both","cat","has","shared","input","and","output",",","and","because","of","cat","and","(","cat1","-",">","cat2",")","is","the","same","Tensor","so","there","is","an","implicit","sharing","here",",","all","tensors","connect","to","cat1","and","cat2","are","in","the","same","sharing","group","after","transitive","sharing","the","difference","is","that","for","this","one",",","all","edges","and","nodes","are","shared","with","the","second","input","edge","of","cat","instead","of","the","first","input","edge","of","cat","as","in","previous","example"]
code: def test_shared_qspec_transitivity_case_2(self):
      """This tests the transitivity of SharedQuantizationSpec, that is
      if A is shared with B, B is shared with C, then C should be shared with A as well
  
      x1 -> conv1 -> cat1 -----> cat2
      x2 -> conv2 -/            /
                     x3 -> add /
                     x4  /
  
      both cat has shared input and output, and because of cat and (cat1 -> cat2) is the same Tensor
      so there is an implicit sharing here, all tensors connect to cat1 and cat2 are in the same
      sharing group after transitive sharing
  
      the difference is that for this one, all edges and nodes are shared with the second input edge of cat
      instead of the first input edge of cat as in previous example
      """
  
      # TODO: refactor this to a common util
      class BackendAQuantizer(Quantizer):
          def annotate(
              self, model: torch.fx.GraphModule
          ) -> torch.fx.GraphModule:
              for node in model.graph.nodes:
                  if (
                      node.op == "call_function"
                      and node.target == torch.ops.aten.conv2d.default
                  ):
                      input_act = node.args[0]
                      assert isinstance(input_act, Node)
                      weight = node.args[1]
                      assert isinstance(weight, Node)
                      bias = node.args[2]
                      assert isinstance(bias, Node)
                      act_qspec = QuantizationSpec(
                          dtype=torch.uint8,
                          quant_min=0,
                          quant_max=255,
                          qscheme=torch.per_tensor_affine,
                          is_dynamic=False,
                          observer_or_fake_quant_ctr=observer.default_observer,
                      )
                      weight_qspec = QuantizationSpec(
                          dtype=torch.int8,
                          quant_min=-128,
                          quant_max=127,
                          qscheme=torch.per_tensor_affine,
                          is_dynamic=False,
                          observer_or_fake_quant_ctr=observer.default_weight_observer,
                      )
                      bias_qspec = QuantizationSpec(
                          dtype=torch.float32,
                          is_dynamic=False,
                          observer_or_fake_quant_ctr=observer.PlaceholderObserver,
                      )
                      node.meta[
                          "quantization_annotation"
                      ] = QuantizationAnnotation(
                          input_qspec_map={
                              input_act: act_qspec,
                              weight: weight_qspec,
                              bias: bias_qspec,
                          },
                          output_qspec=act_qspec,
                          _annotated=True,
                      )
                  elif node.target is torch.ops.aten.cat.default:
                      cat_node = node
                      input_nodes = cat_node.args[0]
                      first_input_node = input_nodes[0]
                      second_input_node = input_nodes[1]
                      input_qspec_map = {}
                      act_qspec = QuantizationSpec(
                          dtype=torch.uint8,
                          quant_min=0,
                          quant_max=255,
                          qscheme=torch.per_tensor_affine,
                          is_dynamic=False,
                          observer_or_fake_quant_ctr=observer.default_observer,
                      )
                      input_qspec_map[second_input_node] = act_qspec
                      share_qparams_with_input_act1_qspec = (
                          SharedQuantizationSpec(
                              (second_input_node, cat_node)
                          )
                      )
                      input_qspec_map[
                          first_input_node
                      ] = share_qparams_with_input_act1_qspec
  
                      cat_node.meta[
                          "quantization_annotation"
                      ] = QuantizationAnnotation(
                          input_qspec_map=input_qspec_map,
                          output_qspec=share_qparams_with_input_act1_qspec,
                          _annotated=True,
                      )
  
          def validate(self, model: torch.fx.GraphModule) -> None:
              pass
  
      self._test_transitive_sharing_with_cat_helper(BackendAQuantizer())
  
imports: from typing import List, Tuple
  import torch
  from torch._export import capture_pre_autograd_graph
  from torch import Tensor
  from torch.ao.quantization import observer, ObserverOrFakeQuantize, QConfigMapping
  from torch.ao.quantization.quantizer import DerivedQuantizationSpec, FixedQParamsQuantizationSpec, QuantizationAnnotation, QuantizationSpec, Quantizer, SharedQuantizationSpec
  from torch.ao.quantization.quantizer.xnnpack_quantizer import XNNPACKQuantizer, get_symmetric_quantization_config
  from torch.ao.quantization.quantizer.xnnpack_quantizer_utils import OP_TO_ANNOTATOR, QuantizationConfig
  from torch.ao.quantization.quantizer.composable_quantizer import ComposableQuantizer
  from torch.ao.quantization.quantizer.embedding_quantizer import EmbeddingQuantizer
  from torch.ao.quantization.quantize_pt2e import convert_pt2e, prepare_pt2e, prepare_qat_pt2e
  from torch.ao.quantization.qconfig import default_per_channel_symmetric_qnnpack_qconfig, float_qparams_weight_only_qconfig, per_channel_weight_observer_range_neg_127_to_127, QConfig, weight_observer_range_neg_127_to_127
  from torch.fx import Node
  from torch.testing._internal.common_quantization import NodeSpec, PT2EQuantizationTestCase, skipIfNoQNNPACK, TestHelperModules
  from torch.testing._internal.common_utils import TemporaryFileName
label_desc: Use image node_id 10 for calling the TestQuantizePT2E obj's underlying member method code with example usage: obj.test_shared_qspec_transitivity_case_2() without return types