code: def test_shared_qspec(self):
      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
  
      m = TestHelperModules.Conv2dWithCat().eval()
      example_inputs = (
          torch.randn(1, 3, 5, 5),
          torch.randn(1, 3, 5, 5),
      )
  
      # program capture
      m = capture_pre_autograd_graph(
          m,
          example_inputs,
      )
      m = prepare_pt2e(m, BackendAQuantizer())
      # make sure the two observers for input are shared
      conv_output_obs = []
      for n in m.graph.nodes:
          if (
              n.op == "call_function"
              and n.target == torch.ops.aten.conv2d.default
          ):
              conv_output_obs.append(
                  getattr(m, next(iter(n.users)).target)
              )
          if (
              n.op == "call_function"
              and n.target == torch.ops.aten.cat.default
          ):
              inputs = n.args[0]
              input0 = inputs[0]
              input1 = inputs[1]
              assert input0.op == "call_module"
              assert input1.op == "call_module"
              obs_ins0 = getattr(m, input0.target)
              obs_ins1 = getattr(m, input1.target)
              assert obs_ins0 == obs_ins1
      assert (
          len(conv_output_obs) == 2
      ), "expecting two observer that follows conv2d ops"
      # checking that the output observers for the two convs are shared as well
      assert conv_output_obs[0] == conv_output_obs[1]
  
      m(*example_inputs)
      m = convert_pt2e(m, fold_quantize=True)
  
      node_occurrence = {
          # two for input of the first conv, one for output for the first conv
          ns.call_function(
              torch.ops.quantized_decomposed.quantize_per_tensor.default
          ): 5,
          ns.call_function(
              torch.ops.quantized_decomposed.dequantize_per_tensor.default
          ): 7,
      }
      node_list = [
          ns.call_function(
              torch.ops.quantized_decomposed.dequantize_per_tensor.default
          ),
          ns.call_function(
              torch.ops.quantized_decomposed.dequantize_per_tensor.default
          ),
          ns.call_function(torch.ops.aten.cat.default),
          ns.call_function(
              torch.ops.quantized_decomposed.quantize_per_tensor.default
          ),
      ]
      self.checkGraphModuleNodes(
          m,
          expected_node_list=node_list,
          expected_node_occurrence=node_occurrence,
      )
  
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 7 for calling the TestQuantizePT2E obj's underlying member method code with example usage: obj.test_shared_qspec() without return types