Add new tile size for fp16 and fallback if vectorization is not possible (#3882)

Add new tile size for fp16 and fallback to none vector version for size not aligned on tile size.
diff --git a/iree/compiler/Conversion/LinalgToSPIRV/KernelDispatchUtils.cpp b/iree/compiler/Conversion/LinalgToSPIRV/KernelDispatchUtils.cpp
index 0600b24..7f0198a 100644
--- a/iree/compiler/Conversion/LinalgToSPIRV/KernelDispatchUtils.cpp
+++ b/iree/compiler/Conversion/LinalgToSPIRV/KernelDispatchUtils.cpp
@@ -61,6 +61,14 @@
   return std::make_tuple(nprocs_x, nprocs / nprocs_x);
 }
 
+namespace {
+struct LaunchConfigInfo {
+  std::array<int64_t, 3> workgroupSize = {1, 1, 1};
+  std::array<int64_t, 3> numSubgroups = {1, 1, 1};
+  bool vectorize = false;
+};
+}  // namespace
+
 /// For a given operation `op`, compute the following configurations according
 /// to SPIR-V `targetEnv` and `options`:
 /// 1) number of tiling levels and tile sizes to use (updates `tileSizes`),
@@ -71,8 +79,7 @@
 static LogicalResult getOpLaunchConfig(T op, const spirv::TargetEnv &targetEnv,
                                        const SPIRVCodegenOptions &options,
                                        TileSizesListType &tileSizes,
-                                       std::array<int64_t, 3> &workgroupSize,
-                                       std::array<int64_t, 3> &numSubgroups) {
+                                       LaunchConfigInfo &config) {
   return op.emitError("undefined launch config for tiled operation");
 }
 
@@ -82,14 +89,13 @@
                                 const spirv::TargetEnv &targetEnv,
                                 const SPIRVCodegenOptions &options,
                                 TileSizesListType &tileSizes,
-                                std::array<int64_t, 3> &workgroupSize,
-                                std::array<int64_t, 3> &numSubgroups) {
+                                LaunchConfigInfo &config) {
   unsigned maxWorkgroupSize = targetEnv.getResourceLimits()
                                   .max_compute_workgroup_invocations()
                                   .getInt();
-  std::tie(workgroupSize[0], workgroupSize[1]) =
+  std::tie(config.workgroupSize[0], config.workgroupSize[1]) =
       distributeProcs2D(maxWorkgroupSize);
-  workgroupSize[2] = 1;
+  config.workgroupSize[2] = 1;
   // This is just being hard-wired for now to be minimal viable, but this can be
   // decided better when we have better estimates of device charecteristics.
   const int64_t nRowsPerWorkitem = 1;
@@ -102,9 +108,9 @@
     tileSizeK = 32;
   }
   assert(tileSizes.empty());
-  SmallVector<int64_t, 4> ts = {nBatchesPerWorkitem,
-                                nRowsPerWorkitem * workgroupSize[1],
-                                nColsPerWorkitem * workgroupSize[0], tileSizeK};
+  SmallVector<int64_t, 4> ts = {
+      nBatchesPerWorkitem, nRowsPerWorkitem * config.workgroupSize[1],
+      nColsPerWorkitem * config.workgroupSize[0], tileSizeK};
   tileSizes.emplace_back(std::move(ts));
   return success();
 }
@@ -209,16 +215,36 @@
     std::array<int64_t, 3> &numSubgroups) {
   if (targetEnv.getVendorID() != spirv::Vendor::ARM) return failure();
 
+  auto lhsType = op.inputs()[0].getType().cast<MemRefType>();
+  auto rhsType = op.inputs()[1].getType().cast<MemRefType>();
+  assert(lhsType.getElementType() == rhsType.getElementType());
+  // Pick ideal tile size based on the type.
+  SmallVector<int64_t, 4> workgroupLevelTs;
+  if (lhsType.getElementType().isF16()) {
+    workgroupLevelTs.append({16, 64, 8});
+  } else {
+    workgroupLevelTs.append({8, 64, 4});
+  }
+
+  // Fall back to the none vectorize path for cases we don't handle.
+  if (!lhsType.hasStaticShape() || !rhsType.hasStaticShape() ||
+      lhsType.getDimSize(0) % workgroupLevelTs[0] != 0 ||
+      rhsType.getDimSize(0) % workgroupLevelTs[1] != 0 ||
+      lhsType.getDimSize(1) % workgroupLevelTs[2] != 0) {
+    return failure();
+  }
+
   workgroupSize[0] = targetEnv.getResourceLimits().subgroup_size().getInt();
   workgroupSize[1] = 1;
   workgroupSize[2] = 1;
-  SmallVector<int64_t, 4> ts = {8, 64, 4};
-  tileSizes.emplace_back(ts);
+  tileSizes.emplace_back(workgroupLevelTs);
   // No tiling at the subgroup level since this target doesn't use subgroup op
   // or shared memory.
   tileSizes.emplace_back();
-  SmallVector<int64_t, 4> threadTs = {ts[0], ts[1] / workgroupSize[0], ts[2]};
-  tileSizes.emplace_back(threadTs);
+  SmallVector<int64_t, 4> invocationLevelTs = {
+      workgroupLevelTs[0], workgroupLevelTs[1] / workgroupSize[0],
+      workgroupLevelTs[2]};
+  tileSizes.emplace_back(invocationLevelTs);
   return success();
 }
 
@@ -227,25 +253,27 @@
                                 const spirv::TargetEnv &targetEnv,
                                 const SPIRVCodegenOptions &options,
                                 TileSizesListType &tileSizes,
-                                std::array<int64_t, 3> &workgroupSize,
-                                std::array<int64_t, 3> &numSubgroups) {
+                                LaunchConfigInfo &config) {
   if (options.enableVectorization &&
       succeeded(getConfigForCooperativeMatmul(op, targetEnv, options, tileSizes,
-                                              workgroupSize, numSubgroups))) {
+                                              config.workgroupSize,
+                                              config.numSubgroups))) {
+    config.vectorize = true;
     return success();
   } else if (options.enableVectorization &&
              succeeded(getTargetSpecificConfig(op, targetEnv, options,
-                                               tileSizes, workgroupSize,
-                                               numSubgroups))) {
+                                               tileSizes, config.workgroupSize,
+                                               config.numSubgroups))) {
+    config.vectorize = true;
     return success();
   }
 
   unsigned maxWorkgroupSize = targetEnv.getResourceLimits()
                                   .max_compute_workgroup_invocations()
                                   .getInt();
-  std::tie(workgroupSize[0], workgroupSize[1]) =
+  std::tie(config.workgroupSize[0], config.workgroupSize[1]) =
       distributeProcs2D(maxWorkgroupSize);
-  workgroupSize[2] = 1;
+  config.workgroupSize[2] = 1;
   const int nRowsPerWorkitem = 1;
   const int nColsPerWorkitem = 1;
   int64_t tileSizeK = 0;
@@ -255,8 +283,9 @@
     tileSizeK = 32;
   }
   assert(tileSizes.empty());
-  SmallVector<int64_t, 4> ts = {nRowsPerWorkitem * workgroupSize[1],
-                                nColsPerWorkitem * workgroupSize[0], tileSizeK};
+  SmallVector<int64_t, 4> ts = {nRowsPerWorkitem * config.workgroupSize[1],
+                                nColsPerWorkitem * config.workgroupSize[0],
+                                tileSizeK};
   tileSizes.emplace_back(std::move(ts));
   return success();
 }
@@ -266,8 +295,7 @@
                                 const spirv::TargetEnv &targetEnv,
                                 const SPIRVCodegenOptions &options,
                                 TileSizesListType &tileSizes,
-                                std::array<int64_t, 3> &workgroupSize,
-                                std::array<int64_t, 3> &numSubgroups) {
+                                LaunchConfigInfo &config) {
   unsigned maxWorkgroupSize = targetEnv.getResourceLimits()
                                   .max_compute_workgroup_invocations()
                                   .getInt();
@@ -275,7 +303,7 @@
   int64_t tileSizeY = maxWorkgroupSize / tileSizeX;
   SmallVector<int64_t, 4> ts = {1, tileSizeY, tileSizeX};
   tileSizes.emplace_back(std::move(ts));
-  workgroupSize = {tileSizeX, tileSizeY, 1};
+  config.workgroupSize = {tileSizeX, tileSizeY, 1};
   return success();
 }
 
@@ -283,8 +311,7 @@
 static LogicalResult getPoolingOpLaunchConfig(
     PoolingOpTy op, const spirv::TargetEnv &targetEnv,
     const SPIRVCodegenOptions &options, TileSizesListType &tileSizes,
-    std::array<int64_t, 3> &workgroupSize,
-    std::array<int64_t, 3> &numSubgroups) {
+    LaunchConfigInfo &config) {
   unsigned maxWorkgroupSize = targetEnv.getResourceLimits()
                                   .max_compute_workgroup_invocations()
                                   .getInt();
@@ -299,7 +326,7 @@
   ts[ts.size() - 2] = tileSizeY;
   ts[ts.size() - 1] = tileSizeX;
   tileSizes.emplace_back(std::move(ts));
-  workgroupSize = {tileSizeX, tileSizeY, 1};
+  config.workgroupSize = {tileSizeX, tileSizeY, 1};
   return success();
 }
 
@@ -308,10 +335,9 @@
   LogicalResult getOpLaunchConfig(                                      \
       opName op, const spirv::TargetEnv &targetEnv,                     \
       const SPIRVCodegenOptions &options, TileSizesListType &tileSizes, \
-      std::array<int64_t, 3> &workgroupSize,                            \
-      std::array<int64_t, 3> &numSubgroups) {                           \
+      LaunchConfigInfo &config) {                                       \
     return getPoolingOpLaunchConfig(op, targetEnv, options, tileSizes,  \
-                                    workgroupSize, numSubgroups);       \
+                                    config);                            \
   }
 
 DEFINE_POOLING_OP_CONFIG(linalg::PoolingMaxOp)
@@ -354,7 +380,7 @@
   spirv::TargetEnv targetEnv(spirv::lookupTargetEnv(*linalgOps.begin()));
 
   Optional<linalg::LinalgOp> rootOperation = {};
-
+  LaunchConfigInfo config;
   for (linalg::LinalgOp linalgOp : linalgOps) {
 #define DISPATCH(opName)                                                  \
   if (auto op = dyn_cast<opName>(linalgOp.getOperation())) {              \
@@ -365,7 +391,7 @@
     rootOperation = linalgOp;                                             \
     TileSizesListType &tileSizesInfo = tileSizes[setKey(*rootOperation)]; \
     if (failed(getOpLaunchConfig(op, targetEnv, options, tileSizesInfo,   \
-                                 workgroupSize, numSubgroups))) {         \
+                                 config))) {                              \
       return failure();                                                   \
     }                                                                     \
     continue;                                                             \
@@ -380,7 +406,9 @@
 
 #undef DISPATCH
   }
-
+  workgroupSize = config.workgroupSize;
+  numSubgroups = config.numSubgroups;
+  vectorize = config.vectorize;
   if (!rootOperation) {
     // No root operations found. Dont need to do anything.
     return success();
diff --git a/iree/compiler/Conversion/LinalgToSPIRV/KernelDispatchUtils.h b/iree/compiler/Conversion/LinalgToSPIRV/KernelDispatchUtils.h
index 17f3bc5..fd3c0a2 100644
--- a/iree/compiler/Conversion/LinalgToSPIRV/KernelDispatchUtils.h
+++ b/iree/compiler/Conversion/LinalgToSPIRV/KernelDispatchUtils.h
@@ -110,6 +110,9 @@
     return !getTileSizes(op, level).empty();
   }
 
+  /// Use vectorize transformations.
+  bool useVectorize() const { return vectorize; }
+
  protected:
   /// Current tile size configuration per operation. They key used here to
   /// retrieve the tile size information per operation is the value of a StrAttr
@@ -125,6 +128,9 @@
   /// Number of subgroups that are logically distributed along x, y & z.
   std::array<int64_t, 3> numSubgroups;
 
+  /// Use vectorization.
+  bool vectorize = false;
+
  private:
   /// Retrieves the key to use to get the `tileSizes` for a given
   /// `operation`. Returns llvm::None on failure.
diff --git a/iree/compiler/Conversion/LinalgToSPIRV/LinalgTileAndFusePass.cpp b/iree/compiler/Conversion/LinalgToSPIRV/LinalgTileAndFusePass.cpp
index 39b6bbf..34c6632 100644
--- a/iree/compiler/Conversion/LinalgToSPIRV/LinalgTileAndFusePass.cpp
+++ b/iree/compiler/Conversion/LinalgToSPIRV/LinalgTileAndFusePass.cpp
@@ -596,7 +596,7 @@
       });
     }
 
-    if (options.enableVectorization) {
+    if (launchConfig.useVectorize()) {
       {
         OwningRewritePatternList secondLevelTilingPatterns;
         populateTilingToSubgroupPatterns(context, launchConfig,
diff --git a/iree/compiler/Conversion/LinalgToSPIRV/test/matmul_fused_vectorization.mlir b/iree/compiler/Conversion/LinalgToSPIRV/test/matmul_fused_vectorization.mlir
index 2bcaf93..33e1131 100644
--- a/iree/compiler/Conversion/LinalgToSPIRV/test/matmul_fused_vectorization.mlir
+++ b/iree/compiler/Conversion/LinalgToSPIRV/test/matmul_fused_vectorization.mlir
@@ -50,3 +50,56 @@
 //      CHECK:         scf.yield
 //  CHECK-COUNT-8:    vector.transfer_write %[[FOR_RES]]
 //          CHECK:    return
+
+// -----
+
+module attributes {
+  spv.target_env =
+    #spv.target_env<#spv.vce<v1.3,
+      [Shader, Float64, Float16, Int64, Int16, Int8, StorageBuffer16BitAccess,
+       StorageUniform16, StoragePushConstant16, StorageBuffer8BitAccess,
+       UniformAndStorageBuffer8BitAccess, StoragePushConstant8, GroupNonUniform,
+       GroupNonUniformVote, GroupNonUniformArithmetic, GroupNonUniformBallot,
+       GroupNonUniformShuffle, GroupNonUniformShuffleRelative, VariablePointers,
+       VariablePointersStorageBuffer],
+      [SPV_KHR_16bit_storage, SPV_KHR_8bit_storage,
+       SPV_KHR_storage_buffer_storage_class, SPV_KHR_variable_pointers]>,
+      ARM:IntegratedGPU,
+      {max_compute_shared_memory_size = 32768 : i32,
+       max_compute_workgroup_invocations = 512 : i32,
+       max_compute_workgroup_size = dense<512> : vector<3xi32>,
+       subgroup_size = 16 : i32}>} {
+  func @matmul_static_shape_f16()
+    attributes {vkspv.num_workgroups_fn = @matmul_static_shape__num_workgroups__} {
+    %arg0 = iree.placeholder for "interface buffer"
+      {binding = @legacy_io::@arg0, operand_result_num = 0 : i32} : memref<4096x4096xf16>
+    %arg1 = iree.placeholder for "interface buffer"
+      {binding = @legacy_io::@arg1, operand_result_num = 1 : i32} : memref<4096x4096xf16>
+    %ret0 = iree.placeholder for "interface buffer"
+      {binding = @legacy_io::@ret0, operand_result_num = 2 : i32} : memref<4096x4096xf16>
+    %cst = constant 0.000000e+00 : f16
+    linalg.fill(%ret0, %cst) : memref<4096x4096xf16>, f16
+    linalg.matmul ins(%arg0, %arg1 : memref<4096x4096xf16>, memref<4096x4096xf16>)
+                 outs(%ret0 : memref<4096x4096xf16>)
+    return
+  }
+  func @matmul_static_shape__num_workgroups__
+    (!shapex.ranked_shape<[4096, 4096]>, !shapex.ranked_shape<[4096, 4096]>,
+     !shapex.ranked_shape<[4096, 4096]>) -> (index, index, index)
+    attributes {sym_visibility = "private"}
+  hal.interface @legacy_io attributes {sym_visibility = "private"} {
+    hal.interface.binding @arg0, set=0, binding=0, type="StorageBuffer", access="Read"
+    hal.interface.binding @arg1, set=0, binding=1, type="StorageBuffer", access="Read"
+    hal.interface.binding @ret0, set=0, binding=2, type="StorageBuffer", access="Write"
+  }
+}
+
+//    CHECK-LABEL: func @matmul_static_shape_f16
+//  CHECK-COUNT-16:   vector.transfer_write
+//  CHECK-COUNT-16:   vector.transfer_read
+//          CHECK:   %[[FOR_RES:.+]]:16 = scf.for
+// CHECK-COUNT-40:     vector.transfer_read
+// CHECK-COUNT-64:     vector.contract
+//      CHECK:         scf.yield
+//  CHECK-COUNT-16:    vector.transfer_write %[[FOR_RES]]
+//          CHECK:    return