Skip to content

[RFC][Quantization] Support quantized models from TensorflowLite #2351

Closed

Description

@FrozenGene

Let me reference @ajtulloch 's comment about quantization workflow firstly:

  1. Implement a model in a standard ML framework, generally using fp16/bfloat16/fp32 compute precision as this has highest throughput on most commonly-used training hardware.

  2. (optionally) insert fake quantization (here, called simulated quantization) nodes at quantization boundaries (i.e. if your backend implements a fused Int8Conv + Int8Relu, you'd insert them after a Conv + Relu block), to simulate the quantization numerics at training time.

  3. Train the model as usual

  4. Implement a graph rewriting pass (i.e. TF's toco, C2's int8_converter, MXNet's quantization, etc) that rewrites the graph to target the int8 operators directly — i.e. remapping subgraphs of e.g. FP32Conv + FP32Relu to be a fused Int8ConvRelu operator. This requires computing output quantization parameters at requantization boundaries, which can be done either by

  • calibration to an example set of activations, via e.g. l-p norm or kl minimization (c2/tf/mxnet/tensorrt)
  • using activation ranges learned during training (c2/tf).
  1. Using this quantized graph, evaluate various metrics to verify the quantization-induced error/loss is acceptable.

  2. Deploy the quantized graph.

However, we have framework can do step 1 -> step 5 well like Tensorflow. For example, Tensorflow has quantization-aware training which will do step 2 and get good accuracy at last.

In the industry development, one common scenario is company will divide algorithm and engine / framework into two different teams. Algorithm team just send an model to engine team to boost the performance. So if algorithm team can use Tensorflow's quantization-aware training, they will know the accuracy before delivering the model to engine team. Engine team just be responsible for boosting the performance.

I will make several PRs to support importing exist quantized model (TFLite INT8 model) In TVM for previous reason. This is not an replacement of #2116, it is just a supplement for TVM's quantization.

After initial investigation and effort, in the Mobilenet V1 model, INT8 can get speed up about 30% when compared with FP32 on ARM CPU.

Welcome any feedback.

Activity

  1. changed the title [-][Quantization] Support quantized models of Tensorflow[/-] [+][RFC][Quantization] Support quantized models from Tensorflow[/+] on Dec 28, 2018
  2. tqchen commented on Dec 31, 2018

    @tqchen
    Member

    Starting from TFLite importer to relay sounds great. cc @jroesch @ajtulloch @yzhliu

  3. ZihengJiang commented on Jan 4, 2019

    @ZihengJiang
    Contributor

    If you want to support transforming quantized model, be careful to transform ops like quantize to small ops like multiply and add for reusing kernels and optimizations like fusion

  4. FrozenGene commented on Jan 4, 2019

    @FrozenGene
    MemberAuthor

    If you want to support transforming quantized model, be careful to transform ops like quantize to small ops like multiply and add for reusing kernels and optimizations like fusion

    Thanks for reminding. However, I don't fully understand your reminder. Do you mean I should be careful quantize or multiply / add ops? If we import existing quantized model like TFLite, we shouldn't see quantize ops any more.

  5. changed the title [-][RFC][Quantization] Support quantized models from Tensorflow[/-] [+][RFC][Quantization] Support quantized models from TensorflowLite[/+] on Jan 8, 2019
  6. jnorwood commented on Mar 19, 2019

    @jnorwood

    Hi, I recently wrote some code to read in the tflite quantized examples and translate them to nnef output. Their operations are pretty similar to nnvm ops. I translated the two mobilenets and the four inception models. There's a cmake config that pulls down all the models and converts them. Please feel free to use whatever you want from it. I forked the NNEF Tools project, https://github.com/jnorwood and put the converter under the contrib/converters/tflite_converters/tflite_to_nnef

    I only added processing for the ops I needed, and I only did quantized data. tflite uses uint8 quantization, btw, with offsets for both weights and features. Biases are int32. NNEF passes quantization configuration in a separate file from the graph. Also, note that tflite uses nhwc everywhere.

  7. anijain2305 commented on May 14, 2019

    @anijain2305
    Contributor

    @FrozenGene I am interested in contributing to this Issue. Is it possible to share the progress?

  8. FrozenGene commented on May 15, 2019

    @FrozenGene
    MemberAuthor

    Hey, @anijain2305 Thanks for your interest. Currently, I am doing #3141. After that, I will start it. BTW, our internal support is based on NNVM and we have completed support it, we have the same result compared with TFLite and have better performance than TFLite. However, I have to spare some time translating to Relay when to make PR. But I have to say that I am busy this month in our product development and it will go to open source progress in my company. I will @ you when that PR is ready.

  9. anijain2305 commented on May 28, 2019

    @anijain2305
    Contributor

    Thanks. Let's lay down the high-level API design for some of the quantized operators. A large portion of this is coming from the following relevant discussions. Thanks to @jackwish, @FrozenGene and @jnorwood for sharing their experiences with quantization, and also @shoubhik for helping design this RFC.

    Other non-TVM related links that were used to understand quantization

    • GemmLowP - Doc
    • TFlite reference code

    Covered frameworks for now - TFLite and MxNet
    Target network for now - Inception V3 from TFLite. (I will create one for Mxnet)
    Target platforms for now - ARM and Intel (will create separate Issue as the project progresses)


    List of required operators - quantize, quantized_conv2d, qunatized_relu, quantized_pool2d, quantized_fully_connected, quantized_concat, dequantize


    It will be good if we can agree on Relay ops - its inputs/outputs and the attributes. The initial proposal for the quantize, quantized_conv2d and dequantize ops is as follows (other quantized_* operators will be on the same lines as that of quantized_conv2d)

    Op quantize

    def quantize(data, scale, zero_point, out_dtype):
        """
        Quantize takes the scale and zero_point attributes and quantizes the 
        FP32 input data to int8/uint8 tensor.
    
        Parameters
        -----------
        data: FP32 tensor
               The input tensor in FP32.
        
        scale: FP32 scalar (An attribute of the op)
               The float scalar to scale the int8 values back to FP32.
    
        zero_point: Int32 zero point (An attribute of the op)
               The zero point of the distribution.
    
        out_dtype: String
               The dtype of the output. Can only be int8/uint8
    
        Returns
        -------
        quantized_data: int8/uint8 tensor
               The quantized tensor.
    
        """

    Key points to discuss

    • The scale and zero_point calculations happen outside the relay graph, i.e., the framework parsers will have to compute the scale and offset if only min and max are provided. Reference implementation in TFLite. This can also be thought as a framework parser utils where we can handle min/max, symmetric/asymmetric etc and generate the scale and zero_point as frameworks handles them.

    Op quantized_conv2d

    def quantized_conv2d(quantized_data, quantized_kernel, 
            input_scale, input_zero_point,
            kernel_scale, kernel_zero_point,
            output_scale, output_zero_point,
            out_dtype,
    
            # All the old remaining ones from conv2d
            strides=(1, 1),
            padding=(0, 0),
            dilation=(1, 1),
            groups=1,
            channels=None,
            kernel_size=None,
            data_layout="NCHW",
            kernel_layout="OIHW",
            out_layout=""):
        """
        
        Quantize takes the scale and zero_point attributes and quantizes the 
        FP32 input data to int8/uint8 tensor. The scale and zero_point calculations
        happen outside the relay graph, i.e., the framework parsers will have to compute
        the scale and offset if only min and max are provided. 
    
        Parameters
        -----------
        quantized_data: int8/uint8 tensor
               The quantized input tensor in int8/uint8.
    
        quantized_kernel: FP32 tensor
               The quantized kernel tensor in int8/uint8.
        
        input_scale: FP32 scalar (An attribute of the op)
               The float scalar to scale the quantized_data int8 values back to FP32.
    
        input_zero_point: Int32 zero point (An attribute of the op)
               The zero point of the quantized_data distribution.
    
        kernel_scale: FP32 scalar (An attribute of the op)
               The float scalar to scale the quantized_kernel int8 values back to FP32.
    
        kernel_zero_point: Int32 zero point (An attribute of the op)
               The zero point of the quantized_kernel distribution.
    
        output_scale: FP32 scalar (An attribute of the op)
               The output scale is set during the quantization process using training/calibration.
               The float scalar to scale the quantized_output int8 values back to FP32.
    
        output_zero_point: Int32 zero point (An attribute of the op)
               The output zero point is set during the quantization process using training/calibration.
               The zero point of the quantized_output distribution.
    
        out_dtype: String
               The dtype of the quantized_output. Can only be int8/uint8.
               The requantization from int32 to int8/uint8 is a part of the op compute.
    
        out_dtype: String
               The dtype of the output. Can only be int8/uint8
    
        ..... Other attributes are same as before.
    
    
        Returns
        -------
        quantized_output: int8/uint8 tensor
               The quantized tensor.
    
        """

    Key points to discuss further

    • This op has a set of computations that can be pre-computed ideally but difficult to do because fold-constant only works on Relay ops and not within a Relay op. This has been discussed in more detail in discuss forum.
      • First pre-computable - The core computation has some compute with kernel (Term 2 and Term 4 in the above link) that will be the part of tvm compute. This is very hard to avoid. We need a fused compute to get the best performance.
      • Second pre-computable - The output scale and zero_point are used to calculate int multiplier and shifts to keep all the computations in Int domain. This computation changes for each op (e.g. concat will handle this in a different manner compared to conv). So, this computation is also kept inside quantized_conv2d op. This can be avoided by changing the API and replacing output_scale with output_multiplier and output_shift. But, this seems very specific to TFLite and one might want to handle the output_scale and output_offset in a different manner. I am not sure about this part, so please comment.
    • The op already has the requantization portion accounted for. As far as I understand, the requantization portion is just a clamp for out_dtype. (The handling of output_multiplier and output_shift, as mentioned above, is for the calculation of output quantized tensor and not for requantization).

    Op dequantize

    Dequantization is required while connecting a quantized operator and an FP32 operator. This might be a temporary stage where we do not have a quantized implementation of the second op. Dequantization might also be required at the end of the network to keep the output of the graph in FP32.

    def dequantize(quantized_data, scale, zero_point, out_dtype):
        """
        Dequantize takes the scale and zero_point attributes and dequantizes the 
        int8/uint8 tensor to FP32 tensor.
    
        Parameters
        -----------
        quantized_data: int8/uint8 quantized input tensor
               The input tensor in int8/uint8.
        
        scale: FP32 scalar (An attribute of the op)
               The float scalar to scale the int8 values back to FP32.
    
        zero_point: Int32 zero point (An attribute of the op)
               The zero point of the distribution.
    
        out_dtype: String
               The dtype of the output. Can only be float32.
    
        Returns
        -------
        data: FP32 tensor
               The dequantized tensor.
    
        """
  10. FrozenGene commented on May 29, 2019

    @FrozenGene
    MemberAuthor

    @anijain2305

    For the q_conv2d, we will add two more arguments.

      output_min=0, 
      output_max=0

    These will be used for restrict the output range, which could be calculated previously. see TFLite's CalculateActivationRangeUint8 function.

    From my experience, we needn't q_relu. But we need q_add / q_concate and so on. I suggest we use MobilenetV2 quant model for example, which is used very widely and have common ops we should consider. For example, depthwise convolution / add / pool and so on.

  11. jnorwood commented on May 29, 2019

    @jnorwood

    From my experience, we needn't q_relu. But we need q_add / q_concate and so on. I suggest we use MobilenetV2 quant model for example,

    Yes, I believe the MobilenetV2 relu_6 is effectively fused in by the downscale saturation. You might need it if you want to support their way of training, though.

    Yes Mobilenet has the q_add, but I suggest the Inceptionv3 for q_concatenate, since it also has concat nodes feeding into concat nodes, and tflite also has to rescale inputs inside the concat operations.

    Also, the MobilenetV2 q_add inputs require rescale... but in both q_concat and q_add you can recalculate the prior op downscale multipliers so you can eliminate the extra rescales.

    Also, depending on your allocation capabilities, you can get rid of all concats.

  12. zhenhuaw-me commented on May 29, 2019

    @zhenhuaw-me
    Contributor

    Hi @anijain2305 regarding the requantization, if the it is not going to put in conv op, the op may suppose to output FP32, otherwise the semantic is confusing. The requantization can convert FP32 to INT8. The multiplier/shift based reuantization approach introduced by TFLite is also adopted by Caffe2/QNNPACK.

    And, maybe we can put the quantization parameters in tensor, as the scale and zero point are describing the INT8 tensor data rather than the op. The op are supposed to read these parameters and get things done.

  13. anijain2305 commented on May 29, 2019

    @anijain2305
    Contributor

    For the q_conv2d, we will add two more arguments.

      output_min=0, 
      output_max=0

    These will be used for restrict the output range, which could be calculated previously.

    I see what you are saying, but I am not sure if this is the right approach. In my opinion, it will be better to put it out of conv. The reason we have these 2 extra min/maxes is because of fused activation in TFLite. It seems better to keep it separate so that both MxNet and TFLite can share quantized_conv2d. In case of TFLite, when we see a fused conv, we can add one more clamp operator in the sequence of ops at the end.

  14. anijain2305 commented on May 29, 2019

    @anijain2305
    Contributor

    Yes, I believe the MobilenetV2 relu_6 is effectively fused in by the downscale saturation. You might need it if you want to support their way of training, though.

    Yes Mobilenet has the q_add, but I suggest the Inceptionv3 for q_concatenate, since it also has concat nodes feeding into concat nodes, and tflite also has to rescale inputs inside the concat operations.

    Make sense. For now, I was thinking of not worrying about depth-wise conv. So, decided to take Inception V3 into account. I think given we are in the starting position, I don't have any big inclination towards any network. My motive is to focus on getting the right infrastructure in the start and showcase it with one large network. The performance micro-optimizations can then phased.

  15. 115 remaining items

  16. tqchen commented on Jul 18, 2019

    @tqchen
    Member

    @anijain2305 can you lead the proposal discussion?

  17. anijain2305 commented on Jul 18, 2019

    @anijain2305
    Contributor

    I agree, we should move the proposal to a new thread.
    Yes, I can lead the proposal discussion.

  18. tqchen commented on Jul 19, 2019

    @tqchen
    Member

    @anijain2305 can you open the RFC thread? Sorry for being a bit formal in this case, we want to set an example for the first dialect public discussions.

  19. anijain2305 commented on Jul 19, 2019

    @anijain2305
    Contributor

    @tqchen Thanks for reminding. Just created one :)

  20. tqchen commented on Jul 20, 2019

    @tqchen
    Member

    Let us move to #3591

  21. u99127 commented on Jul 29, 2019

    @u99127

    Covered frameworks for now - TFLite and MxNet
    Target network for now - Inception V3 from TFLite. (I will create one for Mxnet)
    Target platforms for now - ARM and Intel (will create separate Issue as the project progresses)

    A quick question here since I can't see this mentioned on #3591

    Is this network going to be quantized per tensor as well as the new per-channel quantization that is appearing in tflite 2.0 ? IIUC, tf1.13 has per tensor quantization rather than the per channel quantization. i.e. more interestingly can the relay design support both ?

    https://www.tensorflow.org/lite/performance/quantization_spec?source=post_page---------------------------#per-axis_vs_per-tensor

    regards
    Ramana

    regards
    Ramana

  22. FrozenGene commented on Jul 30, 2019

    @FrozenGene
    MemberAuthor

    Covered frameworks for now - TFLite and MxNet
    Target network for now - Inception V3 from TFLite. (I will create one for Mxnet)
    Target platforms for now - ARM and Intel (will create separate Issue as the project progresses)

    A quick question here since I can't see this mentioned on #3591

    Is this network going to be quantized per tensor as well as the new per-channel quantization that is appearing in tflite 2.0 ? IIUC, tf1.13 has per tensor quantization rather than the per channel quantization. i.e. more interestingly can the relay design support both ?

    https://www.tensorflow.org/lite/performance/quantization_spec?source=post_page---------------------------#per-axis_vs_per-tensor

    regards
    Ramana

    regards
    Ramana

    Good question. We have only supported TF1.13 quantization. TF2.0 has separate scale and doesn't be considered in previous discussion. Seems there is a gap here. cc @anijain2305

  23. added a commit that references this issue on Aug 8, 2019
  24. added a commit that references this issue on Aug 9, 2019
  25. added a commit that references this issue on Sep 6, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions