Beispiel #1
0
    def _TestSkipReshapeQuantization(self, is_training):
        graph = ops.Graph()
        with graph.as_default():
            batch_size, height, width, depth = 5, 128, 128, 3
            input1 = array_ops.zeros((batch_size, height, width, depth))
            conv = conv2d(input1,
                          32, [5, 5],
                          stride=2,
                          padding='SAME',
                          weights_initializer=self._WeightInit(0.09),
                          activation_fn=nn_ops.relu6,
                          scope='test/test')

            reshape = array_ops.reshape(
                conv, (int(10), int(height / 2), int(width / 2), int(16)))

            # Insert a fake quant node after the reshape. We will check that one isn't
            # insert before.
            array_ops.fake_quant_with_min_max_vars(reshape, -1, 1)

            quantize.Quantize(graph,
                              is_training,
                              weight_bits=8,
                              activation_bits=8)

            # Ensure that there isn't a FakeQuant added before the reshape.
            self.assertFalse('FakeQuantWithMinMaxVars' in
                             [i.op.type for i in reshape.op.inputs])

        graph = ops.Graph()
        with graph.as_default():
            batch_size, height, width, depth = 5, 128, 128, 3
            input1 = array_ops.zeros((batch_size, height, width, depth))
            conv = conv2d(input1,
                          32, [5, 5],
                          stride=2,
                          padding='SAME',
                          weights_initializer=self._WeightInit(0.09),
                          activation_fn=nn_ops.relu6,
                          scope='test/test')

            reshape = array_ops.reshape(
                conv, (int(10), int(height / 2), int(width / 2), int(16)))

            # If no fake quant is added after the reshape, a FakeQuant should be added
            # before the reshape.
            quantize.Quantize(graph,
                              is_training,
                              weight_bits=8,
                              activation_bits=8)

            # Ensure that there isn't a FakeQuant added before the reshape.
            self.assertTrue('FakeQuantWithMinMaxVars' in
                            [i.op.type for i in reshape.op.inputs])
Beispiel #2
0
 def _TestLayerActivationQuantized(self, is_training):
     graph = ops.Graph()
     with graph.as_default():
         batch_size, height, width, depth = 5, 128, 128, 3
         input1 = array_ops.zeros((batch_size, height, width, depth))
         _ = conv2d(input1,
                    32, [5, 5],
                    stride=2,
                    padding='SAME',
                    weights_initializer=self._WeightInit(0.09),
                    activation_fn=nn_ops.relu6,
                    biases_initializer=None,
                    scope='test')
         # Ensure that both weights and output of activations are quantized
         # when we have a conv->relu6 with no bias add
         quantize.Quantize(graph,
                           is_training,
                           weight_bits=8,
                           activation_bits=8)
         activation_op = graph.get_operation_by_name('test/Relu6')
         conv_op = graph.get_operation_by_name('test/Conv2D')
         self.assertTrue('test/weights_quant/FakeQuantWithMinMaxVars:0' in
                         [tensor_in.name for tensor_in in conv_op.inputs])
         self.assertTrue(
             'FakeQuantWithMinMaxVars' in
             [op.type for op in activation_op.outputs[0].consumers()])
Beispiel #3
0
    def _TestPostActivationBypassQuantized(self, is_training):
        graph = ops.Graph()
        with graph.as_default():
            batch_size, height, width, depth = 5, 128, 128, 3
            input1 = array_ops.zeros((batch_size, height, width, depth))
            input2 = array_ops.zeros((batch_size, height / 2, width / 2, 32))
            conv = conv2d(input1,
                          32, [5, 5],
                          stride=2,
                          padding='SAME',
                          weights_initializer=self._WeightInit(0.09),
                          activation_fn=nn_ops.relu6,
                          scope='test/test')
            bypass_tensor = math_ops.add(conv, input2, name='test/add')
            # The output of the post_activation bypass will be another layer.
            _ = conv2d(bypass_tensor,
                       32, [5, 5],
                       stride=2,
                       padding='SAME',
                       weights_initializer=self._WeightInit(0.09),
                       activation_fn=nn_ops.relu6,
                       scope='test/unused')

            quantize.Quantize(graph,
                              is_training,
                              weight_bits=8,
                              activation_bits=8)

            # Ensure that the bypass node is preceded by and followed by a
            # FakeQuantWithMinMaxVar operation, since the output of the Add isn't an
            # activation.
            self.assertTrue('FakeQuantWithMinMaxVars' in
                            [c.type for c in bypass_tensor.consumers()])
            self.assertTrue('FakeQuantWithMinMaxVars' in
                            [i.op.type for i in bypass_tensor.op.inputs])
Beispiel #4
0
    def _testWithNonMatchingNameScope(self, is_training):
        graph = ops.Graph()
        with graph.as_default():
            with graph.name_scope('name_scope'):
                batch_size, height, width, depth = 5, 128, 128, 3
                input1 = array_ops.zeros((batch_size, height, width, depth))
                _ = conv2d(input1,
                           32, [5, 5],
                           stride=2,
                           padding='SAME',
                           weights_initializer=self._WeightInit(0.09),
                           activation_fn=None,
                           scope='test')

        op_names_before_quantize = set(
            [op.name for op in graph.get_operations()])
        quantize.Quantize(graph,
                          is_training,
                          weight_bits=8,
                          activation_bits=8,
                          scope='NonExisting/')
        op_names_after_quantize = set(
            [op.name for op in graph.get_operations()])

        # No ops should be inserted or removed.
        self.assertEqual(op_names_before_quantize, op_names_after_quantize)
Beispiel #5
0
    def _testMultiplePartitionedVariables(self, is_training):
        # When weights are partitioned into multiple partitions the weights variable
        # is followed by a identity -> concat -> identity to group the partitions.
        partitioner = partitioned_variables.fixed_size_partitioner(2)
        graph = ops.Graph()
        with graph.as_default():
            with variable_scope.variable_scope('part',
                                               partitioner=partitioner):
                batch_size, height, width, depth = 5, 128, 128, 3
                input1 = array_ops.zeros((batch_size, height, width, depth))
                input2 = array_ops.zeros(
                    (batch_size, height / 2, width / 2, 32))
                conv = conv2d(input1,
                              32, [5, 5],
                              stride=2,
                              padding='SAME',
                              weights_initializer=self._WeightInit(0.09),
                              activation_fn=None,
                              scope='test/test')
                node = math_ops.add(conv, input2, name='test/add')
                node = nn_ops.relu6(node, name='test/relu6')

            quantize.Quantize(graph,
                              is_training,
                              weight_bits=8,
                              activation_bits=8)
            # Check that the weight's quant node was added.
            op_names = [op.name for op in graph.get_operations()]
            self.assertTrue(
                'part/test/test/weights_quant/FakeQuantWithMinMaxVars' in
                op_names)
Beispiel #6
0
    def _TestOverlappingPostActivationBypassQuantized(self, is_training):
        graph = ops.Graph()
        with graph.as_default():
            batch_size, height, width, depth = 5, 128, 128, 3
            conv_input = array_ops.zeros((batch_size, height, width, depth))
            conv1 = conv2d(conv_input,
                           32, [5, 5],
                           stride=2,
                           padding='SAME',
                           weights_initializer=self._WeightInit(0.09),
                           activation_fn=nn_ops.relu6,
                           scope='test/test1')

            # The bypass of this conv is the post activation bypass of the previous
            # conv.
            conv2 = conv2d(conv_input,
                           32, [5, 5],
                           stride=2,
                           padding='SAME',
                           weights_initializer=self._WeightInit(0.09),
                           activation_fn=None,
                           scope='test/test2')

            bypass_tensor = math_ops.add(conv1, conv2, name='test/add')
            _ = nn_ops.relu6(bypass_tensor, name='test/output')

            quantize.Quantize(graph,
                              is_training,
                              weight_bits=8,
                              activation_bits=8)

            # Ensure that the bypass node is preceded by a FakeQuantWithMinMaxVar
            # operation, and NOT followed by one.
            self.assertTrue('FakeQuantWithMinMaxVars' not in
                            [c.type for c in bypass_tensor.consumers()])
            self.assertTrue('FakeQuantWithMinMaxVars' in
                            [i.op.type for i in bypass_tensor.op.inputs])

            # Ensure that all the convs and activations are quantized.
            op_names = [op.name for op in graph.get_operations()]
            self.assertTrue(
                'test/test1/weights_quant/FakeQuantWithMinMaxVars' in op_names)
            self.assertTrue(
                'test/test2/weights_quant/FakeQuantWithMinMaxVars' in op_names)
            self.assertTrue(
                'test/test1/act_quant/FakeQuantWithMinMaxVars' in op_names)
            self.assertTrue(
                'test/act_quant/FakeQuantWithMinMaxVars' in op_names)
            self.assertEqual(
                'Relu6',
                graph.get_operation_by_name(
                    'test/test1/act_quant/FakeQuantWithMinMaxVars').inputs[0].
                op.type)
            self.assertEqual(
                'Relu6',
                graph.get_operation_by_name(
                    'test/act_quant/FakeQuantWithMinMaxVars').inputs[0].op.type
            )
Beispiel #7
0
 def _AssertIdempotent(self, graph):
   # Ensure that calling the rewrite again doesn't change the graph.
   graph_def_before = str(graph.as_graph_def())
   with graph.as_default():
     # Ensuring that calling the rewrite again doesn't add more nodes.
     fold_batch_norms.FoldBatchNorms(graph, is_training=True)
     quantize.Quantize(graph, True)
   graph_def_after = str(graph.as_graph_def())
   self.assertEqual(graph_def_before, graph_def_after)
Beispiel #8
0
def _create_graph(input_graph=None,
                  is_training=True,
                  weight_bits=8,
                  activation_bits=8,
                  symmetric=False,
                  quant_delay=None,
                  freeze_bn_delay=None,
                  scope=None):
    """Rewrites an input_graph in place for simulated quantization.

  The graph has fake quantization ops inserted to simulate the error
  introduced by quantization. Since the graph is transformed in place,
  the expected behavior of previously held references to nodes and tensors may
  change.

  Args:
    input_graph: The tf.Graph to be transformed, if None then defaults to the
      default graph.
    is_training: Whether quantizing training or eval graph.
    weight_bits: Number of bits to use for quantizing weights.
    activation_bits: Number of bits to use for quantizing activations.
    symmetric: If true, use symmetric quantization limits instead of training
      the minimum and maximum of each quantization range separately.
    quant_delay: Number of steps after which weights and activations are
      quantized during training.
    freeze_bn_delay: Number of steps after which moving mean and variance are
      frozen and used instead of batch statistics during training.
      freeze_bn_delay should be greater than quant_delay and should correspond
      to the number of steps when training has almost converged
    scope: The scope to be transformed. If it's not None, only the ops which
      are in this scope will be transformed.

  Raises:
    ValueError: If elements contains an element that isn't a tf.Tensor or
      tf.Operation.
  """

    if input_graph is None:
        input_graph = ops.get_default_graph()

    # Add check to see if graph has training ops, if so provide error message and
    # exit
    _check_for_training_ops(input_graph)
    with input_graph.as_default():
        fold_batch_norms.FoldBatchNorms(
            input_graph,
            freeze_batch_norm_delay=freeze_bn_delay,
            is_training=is_training)
        quantize.Quantize(input_graph,
                          is_training,
                          quant_delay=quant_delay,
                          weight_bits=weight_bits,
                          activation_bits=activation_bits,
                          symmetric=symmetric,
                          scope=scope)
Beispiel #9
0
  def _TestQuantize_AtrousConvWithBatchNorm(
      self, activation, activation_op_name, with_bypass, delay,
      fused_batch_norm, use_resource, scope):
    """Tests quantization: inputs -> atrous conv with batch norm -> Activation.

    Args:
      activation: Callable that returns an Operation, a factory method for the
        Activation.
      activation_op_name: String, name of the Activation operation.
      with_bypass: Bool, when true there is an extra connection added from
        inputs to just before Activation.
      delay: Int (optional), delay in number of steps until quantization starts.
      fused_batch_norm: Bool, when true use FusedBatchNorm.
      use_resource: Bool, when true uses resource variables.
      scope: String, specifies top level scope for the graph
    """
    graph = ops.Graph()
    with graph.as_default():
      variable_scope.get_variable_scope().set_use_resource(use_resource)
      batch_size, height, width, depth = 5, 128, 128, 3
      inputs = array_ops.zeros((batch_size, height, width, depth))
      dilation_rate = 2
      conv_scope = self._GetConvScope(scope, with_bypass)
      scope = '' if scope is None else scope
      delim = '/' if scope else ''

      node = separable_conv2d(
          inputs,
          None, [3, 3],
          rate=dilation_rate,
          depth_multiplier=1.0,
          padding='SAME',
          weights_initializer=self._WeightInit(0.09),
          activation_fn=None,
          normalizer_fn=batch_norm,
          normalizer_params=self._BatchNormParams(fused_batch_norm),
          scope=conv_scope)

      # Manually add a bypass (optional) and an activation.
      if with_bypass:
        node = math_ops.add(inputs, node, name=scope + delim + 'AddV2')

      node = activation(node, name=scope + delim + activation_op_name)

      update_barrier = control_flow_ops.no_op(name='update_barrier')
      with ops.control_dependencies([update_barrier]):
        array_ops.identity(node, name='control_dependency')

      fold_batch_norms.FoldBatchNorms(graph, is_training=True)
      quantize.Quantize(graph, True, quant_delay=delay)

      self._AssertCorrectQuantizedGraphWithBatchNorm(
          graph, scope, 'DepthwiseConv2dNative', activation_op_name,
          with_bypass, delay, use_resource)
Beispiel #10
0
    def _TestInsertQuantOpInSeparableConv2d(self, is_training):
        graph = ops.Graph()
        with graph.as_default():
            batch_size, height, width, depth = 5, 128, 128, 3
            input1 = array_ops.zeros((batch_size, height, width, depth))
            input2 = array_ops.zeros(
                (batch_size, height / 2, width / 2, depth))
            conv = separable_conv2d(input1,
                                    3, [5, 5],
                                    stride=2,
                                    depth_multiplier=1.0,
                                    padding='SAME',
                                    weights_initializer=self._WeightInit(0.09),
                                    activation_fn=None,
                                    scope='test/test')
            node = math_ops.add(conv, input2, name='test/add')
            node = nn_ops.relu6(node, name='test/relu6')
            update_barrier = control_flow_ops.no_op(name='update_barrier')
            with ops.control_dependencies([update_barrier]):
                array_ops.identity(node, name='control_dependency')

        quantize.Quantize(graph, is_training, weight_bits=8, activation_bits=8)
        # Check if output of bias add is quantized
        quantization_node_name = 'FakeQuantWithMinMaxVars'
        conv_quant = graph.get_operation_by_name('test/test/conv_quant/' +
                                                 quantization_node_name)
        self.assertEqual(conv_quant.type, quantization_node_name)

        # Check if weights for both convs inside seperable conv are quantized
        pointwise_weight_quant = graph.get_operation_by_name(
            'test/test/weights_quant/' + quantization_node_name)
        self.assertEqual(pointwise_weight_quant.type, quantization_node_name)
        depthwise_weight_quant = graph.get_operation_by_name(
            'test/test/separable_conv2d/weights_quant/' +
            quantization_node_name)
        self.assertEqual(depthwise_weight_quant.type, quantization_node_name)

        # Check if activations after first depthwise conv are quantized.
        depthwise_act_quant = graph.get_operation_by_name(
            'test/test/separable_conv2d/act_quant/' + quantization_node_name)
        self.assertEqual(depthwise_act_quant.type, quantization_node_name)

        for op in graph.get_operations():
            if op.type == quantization_node_name:
                quant_op = graph.get_operation_by_name(op.name)
                # Scan through all FakeQuant operations, ensuring that the activation
                # identity op isn't in the consumers of the operation.
                consumers = []
                for output in quant_op.outputs:
                    consumers.extend(output.consumers())

                self.assertNotIn('test/relu6', [c.name for c in consumers])
Beispiel #11
0
  def _TestBatchNormForcedUpdates(self, activation, activation_op_name,
                                  fused_batch_norm, use_resource):
    """post_activation bypass quantization should happen with forced updates."""
    graph = ops.Graph()
    with graph.as_default():
      variable_scope.get_variable_scope().set_use_resource(use_resource)
      batch_size, height, width, depth = 5, 128, 128, 3
      input1 = array_ops.zeros((batch_size, height, width, depth))
      input2 = array_ops.zeros((batch_size, height / 2, width / 2, 32))
      # Setting updates_collections to None forces updates adding an extra
      # identity operation following batch norms.
      bn_params = self._BatchNormParams(
          fused=fused_batch_norm, force_updates=True)
      conv = conv2d(
          input1,
          32, [5, 5],
          stride=2,
          padding='SAME',
          weights_initializer=self._WeightInit(0.09),
          activation_fn=activation,
          normalizer_fn=batch_norm,
          normalizer_params=bn_params,
          scope='test/test')
      bypass_tensor = math_ops.add(conv, input2, name='test/add')
      # The output of the post_activation bypass will be another layer.
      _ = conv2d(
          bypass_tensor,
          32, [5, 5],
          stride=2,
          padding='SAME',
          weights_initializer=self._WeightInit(0.09),
          normalizer_fn=batch_norm,
          normalizer_params=bn_params,
          activation_fn=activation,
          scope='test/unused')

      fold_batch_norms.FoldBatchNorms(graph, is_training=True)
      quantize.Quantize(graph, is_training=True)

      # Ensure that the bypass node is preceded by and followed by a
      # FakeQuantWithMinMaxVar operation, since the output of the Add isn't an
      # activation.
      self.assertTrue('FakeQuantWithMinMaxVars' in
                      [c.type for c in bypass_tensor.consumers()])
      self.assertTrue('FakeQuantWithMinMaxVars' in
                      [i.op.type for i in bypass_tensor.op.inputs])

    with open('/tmp/bn_quant_test.pbtxt', 'w') as f:
      f.write(str(graph.as_graph_def()))
Beispiel #12
0
  def _TestQuantize_Conv2dWithoutBatchNorm(self, activation, activation_op_name,
                                           with_bypass, delay, use_resource,
                                           scope):
    """Tests quantization: inputs -> Conv2d no batch norm -> Activation.

    Args:
      activation: Callable that returns an Operation, a factory method for the
        Activation.
      activation_op_name: String, name of the Activation operation.
      with_bypass: Bool, when true there is an extra connection added from
        inputs to just before Activation.
      delay: Int (optional), delay in number of steps until quantization starts.
      use_resource: Bool, when true uses resource variables.
      scope: String, specifies top level scope for the graph
    """
    graph = ops.Graph()
    with graph.as_default():
      variable_scope.get_variable_scope().set_use_resource(use_resource)
      batch_size, height, width, depth = 5, 128, 128, 3
      inputs = array_ops.zeros((batch_size, height, width, depth))
      stride = 1 if with_bypass else 2
      out_depth = 3 if with_bypass else 32
      activation_fn = None if with_bypass else activation
      conv_scope = self._GetConvScope(scope, with_bypass)
      scope = '' if scope is None else scope
      delim = '/' if scope else ''
      node = conv2d(
          inputs,
          out_depth, [5, 5],
          stride=stride,
          padding='SAME',
          weights_initializer=self._WeightInit(0.09),
          activation_fn=activation_fn,
          scope=conv_scope)
      if with_bypass:
        node = math_ops.add(inputs, node, name=scope + delim + 'AddV2')
        node = activation(node, name=scope + delim + activation_op_name)
      update_barrier = control_flow_ops.no_op(name='update_barrier')
      with ops.control_dependencies([update_barrier]):
        array_ops.identity(node, name='control_dependency')

      quantize.Quantize(graph, True, quant_delay=delay)

    if conv_scope is None:
      conv_scope = ''

    self._AssertCorrectQuantizedGraphWithoutBatchNorm(
        graph, scope, 'Conv2D', activation_op_name, with_bypass, delay,
        use_resource)
Beispiel #13
0
    def _TestWithNullNameScope(self, is_training):
        graph = ops.Graph()
        with graph.as_default():
            with graph.name_scope(None):
                batch_size, height, width, depth = 5, 128, 128, 32
                input1 = array_ops.zeros((batch_size, height, width, depth))
                _ = conv2d(input1,
                           32, [5, 5],
                           padding='SAME',
                           weights_initializer=self._WeightInit(0.09),
                           activation_fn=None,
                           scope='test')

                quantize.Quantize(graph,
                                  is_training,
                                  weight_bits=8,
                                  activation_bits=8)
Beispiel #14
0
    def testSeparableConvWithResourceVar(self):
        graph = ops.Graph()
        with graph.as_default():
            with variable_scope.variable_scope('', use_resource=True):
                batch_size, height, width, depth = 5, 128, 128, 3
                input1 = array_ops.zeros((batch_size, height, width, depth))
                kernel_size, depth_multiplier = 3, 1
                depthwise_shape = [
                    kernel_size, kernel_size, depth, depth_multiplier
                ]
                depthwise_weights = variables.model_variable(
                    'depthwise_weights', shape=depthwise_shape)
                strides = [1, 1, 1, 1]
                with variable_scope.variable_scope('depthwise_conv_1'):
                    conv1 = nn.depthwise_conv2d(input1,
                                                depthwise_weights,
                                                strides,
                                                padding='SAME')
                with variable_scope.variable_scope('depthwise_conv_2'):
                    conv2 = nn.depthwise_conv2d(conv1,
                                                depthwise_weights,
                                                strides,
                                                padding='SAME')
                    math_ops.add(conv2, input1, name='add')

        quantize.Quantize(graph, True)

        # Test that the weights and activations of all convs have been quantized.
        quant_node_name = 'FakeQuantWithMinMaxVars'
        weights_quant = graph.get_operation_by_name(
            'depthwise_conv_1/weights_quant/' + quant_node_name)
        self.assertEqual(weights_quant.type, quant_node_name)
        act_quant = graph.get_operation_by_name('depthwise_conv_1/act_quant/' +
                                                quant_node_name)
        self.assertEqual(act_quant.type, quant_node_name)

        weights_quant = graph.get_operation_by_name(
            'depthwise_conv_2/weights_quant/' + quant_node_name)
        self.assertEqual(weights_quant.type, quant_node_name)
        act_quant = graph.get_operation_by_name('depthwise_conv_2/act_quant/' +
                                                quant_node_name)
        self.assertEqual(act_quant.type, quant_node_name)
Beispiel #15
0
 def _TestFinalLayerQuantized(self, is_training):
     graph = ops.Graph()
     with graph.as_default():
         batch_size, height, width, depth = 5, 128, 128, 3
         input1 = array_ops.zeros((batch_size, height, width, depth))
         _ = conv2d(input1,
                    32, [5, 5],
                    stride=2,
                    padding='SAME',
                    weights_initializer=self._WeightInit(0.09),
                    activation_fn=None,
                    scope='test')
         # Ensure that the a FakeQuant operation is in the outputs of the BiasAdd.
         bias_add_op = graph.get_operation_by_name('test/BiasAdd')
         quantize.Quantize(graph,
                           is_training,
                           weight_bits=8,
                           activation_bits=8)
         self.assertTrue(
             'FakeQuantWithMinMaxVars' in
             [op.type for op in bias_add_op.outputs[0].consumers()])
Beispiel #16
0
    def _TestWithNameScope(self, is_training):
        graph = ops.Graph()
        with graph.as_default():
            with graph.name_scope('name_scope'):
                batch_size, height, width, depth = 5, 128, 128, 3
                input1 = array_ops.zeros((batch_size, height, width, depth))
                _ = conv2d(input1,
                           32, [5, 5],
                           stride=2,
                           padding='SAME',
                           weights_initializer=self._WeightInit(0.09),
                           activation_fn=None,
                           scope='test')

                quantize.Quantize(graph,
                                  is_training,
                                  weight_bits=8,
                                  activation_bits=8)

        for op in graph.get_operations():
            self.assertTrue(not op.name.startswith('name_scope/name_scope/'),
                            'Broken op: %s' % op.name)
Beispiel #17
0
    def _TestInsertQuantOpForAddAfterConv2d(self, is_training):
        graph = ops.Graph()
        with graph.as_default():
            batch_size, height, width, depth = 5, 128, 128, 3
            input1 = array_ops.zeros((batch_size, height, width, depth))
            input2 = array_ops.zeros((batch_size, height / 2, width / 2, 32))
            conv = conv2d(input1,
                          32, [5, 5],
                          stride=2,
                          padding='SAME',
                          weights_initializer=self._WeightInit(0.09),
                          activation_fn=None,
                          scope='test/test')
            node = math_ops.add(conv, input2, name='test/add')
            node = nn_ops.relu6(node, name='test/relu6')
            update_barrier = control_flow_ops.no_op(name='update_barrier')
            with ops.control_dependencies([update_barrier]):
                array_ops.identity(node, name='control_dependency')

        quantize.Quantize(graph, is_training, weight_bits=8, activation_bits=8)

        quantization_node_name = 'FakeQuantWithMinMaxVars'
        conv_quant = graph.get_operation_by_name('test/test/conv_quant/' +
                                                 quantization_node_name)
        self.assertEqual(conv_quant.type, quantization_node_name)

        # Scan through all FakeQuant operations, ensuring that the activation
        # isn't in the consumers of the operation. Since activations are folded
        # the preceding operation during inference, the FakeQuant operation after
        # the activation is all that is needed.
        for op in graph.get_operations():
            if op.type == quantization_node_name:
                quant_op = graph.get_operation_by_name(op.name)
                consumers = []
                for output in quant_op.outputs:
                    consumers.extend(output.consumers())

                self.assertNotIn('test/relu6', [c.name for c in consumers])