Nerotos opened a new issue, #14835:
URL: https://github.com/apache/tvm/issues/14835

   Hello,
   
   I have started working on QuantizeInferCorrectLayout as I need to use 
Quantize with an NHWC layout, but I can't figure out how to implement this. I 
started by just copying over the version requantize.cc
   
   ### Expected behavior
   
   After the ConvertLayoutpass I want to have all data in NHWC format 
throughout the IR.
   
   ### Actual behavior
   Some elements are not in the correct layout. See calculation of %5 and %6.
   ```
   fn (%layer1_input: Tensor[(1, 16, 16, 16), float32] /* ty=Tensor[(1, 16, 16, 
16), float32] */, %c1_weight: Tensor[(16, 16, 3, 3), float32] /* ty=Tensor[(16, 
16, 3, 3), float32] */, %c1_bias: Tensor[(16), float32] /* ty=Tensor[(16), 
float32] */) -> Tensor[(1, 16, 14, 14), uint8] {
     %0 = qnn.quantize(%layer1_input, 0.00786161f /* ty=float32 */, 0 /* 
ty=int32 */, out_dtype="uint8", axis=1) /* ty=Tensor[(1, 16, 16, 16), uint8] */;
     %1 = qnn.quantize(%c1_weight, meta[relay.Constant][0] /* ty=Tensor[(16), 
float32] */, 0 /* ty=int32 */, out_dtype="int8", axis=0) /* ty=Tensor[(16, 16, 
3, 3), int8] */;
     %2 = layout_transform(%0, src_layout="NCHW", dst_layout="NHWC") /* 
ty=Tensor[(1, 16, 16, 16), uint8] */;
     %3 = layout_transform(%1, src_layout="OIHW", dst_layout="HWIO") /* 
ty=Tensor[(3, 3, 16, 16), int8] */;
     %4 = qnn.conv2d(%2, %3, 0 /* ty=int32 */, 0 /* ty=int32 */, 0.00786161f /* 
ty=float32 */, meta[relay.Constant][0] /* ty=Tensor[(16), float32] */, 
padding=[0, 0, 0, 0], channels=16, kernel_size=[3, 3], data_layout="NHWC", 
kernel_layout="HWIO", out_dtype="int32") /* ty=Tensor[(1, 14, 14, 16), int32] 
*/;
     %5 = layout_transform(%4, src_layout="NHWC", dst_layout="NCHW") /* 
ty=Tensor[(1, 16, 14, 14), int32] */;
     %6 = qnn.quantize(%c1_bias, meta[relay.Constant][1] /* ty=Tensor[(16), 
float32] */, 0 /* ty=int32 */, out_dtype="int32", axis=0) /* ty=Tensor[(16), 
int32] */;
     %7 = nn.bias_add(%5, %6) /* ty=Tensor[(1, 16, 14, 14), int32] */;
     %8 = qnn.requantize(%7, meta[relay.Constant][2] /* ty=Tensor[(16), 
float32] */, 0 /* ty=int32 */, 0.0175786f /* ty=float32 */, 64 /* ty=int32 */, 
axis=1, out_dtype="int32") /* ty=Tensor[(1, 16, 14, 14), int32] */;
     %9 = clip(%8, a_min=0f, a_max=255f) /* ty=Tensor[(1, 16, 14, 14), int32] 
*/;
     cast(%9, dtype="uint8") /* ty=Tensor[(1, 16, 14, 14), uint8] */
   } /* ty=fn (Tensor[(1, 16, 16, 16), float32], Tensor[(16, 16, 3, 3), 
float32], Tensor[(16), float32]) -> Tensor[(1, 16, 14, 14), uint8] */
   ```
   
   
   ### Environment
   
   Linux OS
   TVM main branch
   
   ### Steps to reproduce
   
   Current code for QuantizeInferCorrectLayout, from requantize. Copy into 
tmv/src/relay/qnn/op/quantize.cc:
   
   ```
   InferCorrectLayoutOutput QuantizeInferCorrectLayout(const Attrs& attrs,
                                                         const Array<Layout>& 
new_in_layouts,
                                                         const Array<Layout>& 
old_in_layouts,
                                                         const 
Array<tvm::relay::Type>& old_in_types) {
     const auto* attrs_ptr = attrs.as<QuantizeAttrs>();
     ICHECK(attrs_ptr);
     ObjectPtr<QuantizeAttrs> param = make_object<QuantizeAttrs>(*attrs_ptr);
   
   
     Array<Array<IndexExpr>> old_in_shapes;
     for (auto old_in_t : old_in_types) {
       ICHECK(old_in_t.as<TensorTypeNode>());
       old_in_shapes.push_back(old_in_t.as<TensorTypeNode>()->shape);
     }
   
     Array<Layout> input_layouts, output_layouts;
     if (new_in_layouts.defined()) {
       // Adapt to new layout. The axis has to change.
       // Record original reduce axis. Convert to the modified layout axis.
       //ICHECK_EQ(new_in_layouts.size(), 3);
       ICHECK_EQ(old_in_layouts.size(), 3);
   
       // 1) Get the axis.
       int axis = param->axis;
       axis = (axis == -1) ? old_in_shapes[0].size() - 1 : axis;
   
       // 2) Collect the original axis
       std::string old_dim = old_in_layouts[0][axis].name();
   
       // 3) Collect the new axes by walking new_layout.
       tvm::Integer new_axis;
       std::string new_layout_string = "";
       int axis_index = 0;
       for (auto iter_var : new_in_layouts[0]->axes) {
         const auto& layout_axis = LayoutAxis::Get(iter_var);
         const std::string& layout_dim = layout_axis.name();
         if (old_dim == layout_dim) {
           new_axis = tvm::Integer(axis_index);
         }
   
         if (layout_axis.IsPrimal()) {
           new_layout_string += layout_dim;
           axis_index++;
         } else {
           // Propagate layout if input_zero_point and input_scale are scalar 
values.
           ICHECK_GE(old_in_types.size(), 3);
           if (IsScalarType(old_in_types[1]) && IsScalarType(old_in_types[2])) {
             new_layout_string += 
std::to_string(new_in_layouts[0].FactorOf(layout_axis)) + layout_dim;
             axis_index++;
           }
         }
       }
   
       // 4) Set the new axis and layout.
       Layout new_layout = Layout(new_layout_string);
   
       // Fill the layouts of remaining input tensors - scales and zero points. 
The layouts of these
       // tensors can be treated as channel layout.
       Layout channel_layout = Layout("C");
       input_layouts = {new_layout, channel_layout, channel_layout, 
channel_layout, channel_layout};
       output_layouts = {new_layout};
       param->axis = new_axis.IntValue();
     } else if (old_in_layouts.defined()) {
       // If the new layout is undefined, set the old layout as the inferred 
layout.
       ICHECK_EQ(old_in_layouts.size(), 3);
   
       Layout old_layout = old_in_layouts[0];
   
       // Fill the layouts of remaining input tensors - scales and zero points. 
The layouts of these
       // tensors can be treated as channel layout.
       Layout channel_layout = Layout("C");
       input_layouts = {old_layout, channel_layout, channel_layout, 
channel_layout, channel_layout};
       output_layouts = {old_layout};
     } else {
       // Set the layouts to undef.
       Layout undef = Layout::Undef();
       input_layouts = Array<Layout>(3, undef);
       output_layouts = {undef};
     }
   
     return InferCorrectLayoutOutput(input_layouts, output_layouts, 
Attrs(param));
   }
   ```
   
   An example script to import a model from pytorch and apply the 
transformations:
   
   ```
   import numpy as np
   from torch import nn
   from torch.ao.quantization.quantize_fx import prepare_fx, convert_fx
   from torch.ao.quantization.observer import MinMaxObserver
   import torch
   import os
   from tvm import relay
   import tvm
   
   batch_size = 1
   input_channels = 16
   input_height = 16
   input_width = 16
   output_channels = 16
   kernel_size = 3
   stride = 1
   num_classes = 1
   bias = False
   
   class ConvModel(nn.Module):
       def __init__(self, in_channels, out_channels, kernel_size, 
num_classes=0):
           super().__init__()
           self.quant = torch.ao.quantization.QuantStub()
           self.c1 = nn.Conv2d(in_channels, out_channels, kernel_size)
       
       def forward(self, x):
           x = self.quant(x)
           x = self.c1(x)
           return x
   
   # Quantize the model
   input_fp = torch.rand(1, input_channels, input_height, input_width)
   model = ConvModel(input_channels, output_channels, kernel_size)
   model.eval()
   
   # Specify quantization configuration
   # Start with simple min/max range estimation and per-tensor quantization of 
weights
   #model.qconfig = torch.ao.quantization.get_default_qconfig("x86")
   model.qconfig = torch.ao.quantization.QConfig(
       activation=MinMaxObserver.with_args(dtype=torch.quint8),
       weight=MinMaxObserver.with_args(dtype=torch.qint8))
   
   torch.ao.quantization.prepare(model, inplace=True)
   
   # Pseudo-Calibration
   model(input_fp)
   
   #Convert to quantized model
   torch.ao.quantization.convert(model, inplace=True)
   
   # Save the model.
   torch.jit.save(torch.jit.script(model), 
"conv2d_model_scripted_quantized.pth")
   
   os.system("rm -rf model.tar dev/ include/ generated-project/")
   os.system("mkdir -p include")
   
   #Generate expected output data
   input_matrix = np.random.randint(0, high=127, size=(1, input_channels, 
input_height, input_width), dtype=np.int8)
   input_float = torch.rand(1, input_channels, input_height, input_width)
   input_quant = input_float
   expected_output = model(input_quant)
   
   
   ##################################
   # Compiling the model with TVM
   # --------------------------------
   #
   
   # For importing from Torch we need to trace the model beforehand
   input_name = "layer1_input"
   input_shapes = [(input_name, (batch_size, input_channels, input_height, 
input_width))]
   input_dtype = "uint8"
   
   script_module = torch.jit.trace(model, input_quant).eval()
   mod, params = relay.frontend.from_pytorch(script_module, input_shapes)
   
   mod = gemmini.preprocess_pass(mod)
   mod = relay.transform.InferType()(mod)
   mod = relay.transform.ConvertLayout({"qnn.conv2d": ["NHWC", "HWIO"], 
                                        "nn.conv2d": ["NHWC", "HWIO"]})(mod)
   mod = relay.transform.SimplifyExpr()(mod)
   ```
   
   ### Triage
   
   
   * needs-triage
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to