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
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"]
code: def test_shared_qspec_transitivity(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
      """
  
      # 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]
                      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[first_input_node] = act_qspec
                      share_qparams_with_input_act0_qspec = (
                          SharedQuantizationSpec(
                              (first_input_node, cat_node)
                          )
                      )
                      for input_node in input_nodes[1:]:
                          input_qspec_map[
                              input_node
                          ] = share_qparams_with_input_act0_qspec
  
                      cat_node.meta[
                          "quantization_annotation"
                      ] = QuantizationAnnotation(
                          input_qspec_map=input_qspec_map,
                          output_qspec=share_qparams_with_input_act0_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 9 for calling the TestQuantizePT2E obj's underlying member method code with example usage: obj.test_shared_qspec_transitivity() without return types