text: #ifndef DRIVER_CONTRACTION_DLOPS_V1R2_HPP
  #define DRIVER_CONTRACTION_DLOPS_V1R2_HPP
  
  #include "common_header.hpp"
  #include "tensor_descriptor.hpp"
  #include "tensor_descriptor_helper.hpp"
  #include "gridwise_contraction_dlops_v1r2.hpp"
  
  template <ck::index_t BlockSize,
            typename FloatAB,
            typename FloatAcc,
            typename FloatC,
            ck::InMemoryDataOperationEnum_t CGlobalMemoryDataOperation,
            typename AGridDesc_GK0_GM0_GM1_GK1,
            typename BGridDesc_GK0_GN0_GN1_GK1,
            typename CGridDesc_GM0_GM1_GN0_GN1,
            ck::index_t GM1PerBlockGM11,
            ck::index_t GN1PerBlockGN11,
            ck::index_t GK0PerBlock,
            ck::index_t BM1PerThreadBM11,
            ck::index_t BN1PerThreadBN11,
            ck::index_t BK0PerThread,
            typename BM10BN10ThreadClusterBM10Xs,
            typename BM10BN10ThreadClusterBN10Xs,
            typename ABlockTransferThreadSliceLengths_GK0_GM0_GM10_GM11_GK1,
            typename ABlockTransferThreadClusterLengths_GK0_GM0_GM10_GM11_GK1,
            typename ABlockTransferThreadClusterArrangeOrder,
            typename ABlockTransferSrcAccessOrder,
            typename ABlockTransferSrcVectorTensorLengths_GK0_GM0_GM10_GM11_GK1,
            typename ABlockTransferDstVectorTensorLengths_GK0_GM0_GM10_GM11_GK1,
            typename ABlockTransferSrcVectorTensorContiguousDimOrder,
            typename BBlockTransferThreadSliceLengths_GK0_GN0_GN10_GN11_GK1,
            typename BBlockTransferThreadClusterLengths_GK0_GN0_GN10_GN11_GK1,
            typename BBlockTransferThreadClusterArrangeOrder,
            typename BBlockTransferSrcAccessOrder,
            typename BBlockTransferSrcVectorTensorLengths_GK0_GN0_GN10_GN11_GK1,
            typename BBlockTransferDstVectorTensorLengths_GK0_GN0_GN10_GN11_GK1,
            typename BBlockTransferSrcVectorTensorContiguousDimOrder,
            typename CThreadTransferSrcDstAccessOrder,
            ck::index_t CThreadTransferSrcDstVectorDim,
            ck::index_t CThreadTransferDstScalarPerVector,
            typename AGridStepHacks,
            typename BGridStepHacks,
            typename CGridStepHacks,
            typename AGridMoveSliceWindowStepHacks,
            typename BGridMoveSliceWindowStepHacks>
  __host__ float
  driver_contraction_dlops_v1r2(const FloatAB* p_a_grid,
                                const FloatAB* p_b_grid,
                                FloatC* p_c_grid,
                                const AGridDesc_GK0_GM0_GM1_GK1& a_grid_desc_gk0_gm0_gm1_gk1,
                                const BGridDesc_GK0_GN0_GN1_GK1& b_grid_desc_gk0_gn0_gn1_gk1,
                                const CGridDesc_GM0_GM1_GN0_GN1& c_grid_desc_gm0_gm1_gn0_gn1,
                                AGridStepHacks,
                                BGridStepHacks,
                                CGridStepHacks,
                                AGridMoveSliceWindowStepHacks,
                                BGridMoveSliceWindowStepHacks,
                                ck::index_t nrepeat)
  
  {
      using namespace ck;
  
      constexpr auto I0 = Number<0>{};
      constexpr auto I1 = Number<1>{};
      constexpr auto I2 = Number<2>{};
      constexpr auto I3 = Number<3>{};
      constexpr auto I4 = Number<4>{};
      constexpr auto I5 = Number<5>{};
  
      // GEMM
      using GridwiseContraction =
          GridwiseContractionDlops_A_GK0_GM0_GM1_GK1_B_GK0_GN0_GN1_GK1_C_GM0_GM1_GN0_GN1<
              BlockSize,
              FloatAB,
              FloatAcc,
              FloatC,
              CGlobalMemoryDataOperation,
              AGridDesc_GK0_GM0_GM1_GK1,
              BGridDesc_GK0_GN0_GN1_GK1,
              CGridDesc_GM0_GM1_GN0_GN1,
              GM1PerBlockGM11,
              GN1PerBlockGN11,
              GK0PerBlock,
              BM1PerThreadBM11,
              BN1PerThreadBN11,
              BK0PerThread,
              BM10BN10ThreadClusterBM10Xs,
              BM10BN10ThreadClusterBN10Xs,
              ABlockTransferThreadSliceLengths_GK0_GM0_GM10_GM11_GK1,
              ABlockTransferThreadClusterLengths_GK0_GM0_GM10_GM11_GK1,
              ABlockTransferThreadClusterArrangeOrder,
              ABlockTransferSrcAccessOrder,
              ABlockTransferSrcVectorTensorLengths_GK0_GM0_GM10_GM11_GK1,
              ABlockTransferDstVectorTensorLengths_GK0_GM0_GM10_GM11_GK1,
              ABlockTransferSrcVectorTensorContiguousDimOrder,
              BBlockTransferThreadSliceLengths_GK0_GN0_GN10_GN11_GK1,
              BBlockTransferThreadClusterLengths_GK0_GN0_GN10_GN11_GK1,
              BBlockTransferThreadClusterArrangeOrder,
              BBlockTransferSrcAccessOrder,
              BBlockTransferSrcVectorTensorLengths_GK0_GN0_GN10_GN11_GK1,
              BBlockTransferDstVectorTensorLengths_GK0_GN0_GN10_GN11_GK1,
              BBlockTransferSrcVectorTensorContiguousDimOrder,
              CThreadTransferSrcDstAccessOrder,
              CThreadTransferSrcDstVectorDim,
              CThreadTransferDstScalarPerVector,
              AGridStepHacks,
              BGridStepHacks,
              CGridStepHacks,
              AGridMoveSliceWindowStepHacks,
              BGridMoveSliceWindowStepHacks>;
  
      const auto GK0 = a_grid_desc_gk0_gm0_gm1_gk1.GetLength(I0);
  
      if(!GridwiseContraction::CheckValidity(
             a_grid_desc_gk0_gm0_gm1_gk1, b_grid_desc_gk0_gn0_gn1_gk1, c_grid_desc_gm0_gm1_gn0_gn1))
      {
          throw std::runtime_error("wrong! "
                                   "GridwiseContraction_A_GK0_GM0_GM1_GK1_B_GK0_GN0_GN1_GK1_C_"
                                   "GM0_GM1_GN0_GN1 has invalid setting");
      }
  
      const auto a_grid_desc_gk0_gm0_gm10_gm11_gk1 =
          GridwiseContraction::MakeAGridDescriptor_GK0_GM0_GM10_GM11_GK1(a_grid_desc_gk0_gm0_gm1_gk1);
      const auto b_grid_desc_gk0_gn0_gn10_gn11_gk1 =
          GridwiseContraction::MakeBGridDescriptor_GK0_GN0_GN10_GN11_GK1(b_grid_desc_gk0_gn0_gn1_gk1);
  
      using AGridDesc_GK0_GM0_GM10_GM11_GK1 = decltype(a_grid_desc_gk0_gm0_gm10_gm11_gk1);
      using BGridDesc_GK0_GN0_GN10_GN11_GK1 = decltype(b_grid_desc_gk0_gn0_gn10_gn11_gk1);
  
      // c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1
      const auto c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1 =
          GridwiseContraction::MakeCGridDescriptor_GM10_BM0_BM1_GN10_BN0_BN1(
              c_grid_desc_gm0_gm1_gn0_gn1);
  
      using CGridDesc_GM10_BM0_BM1_GN10_BN0_BN1 = decltype(c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1);
  
      // c_grid_block_cluster_blockid_to_gm10_gn10
      const auto c_grid_block_cluster_blockid_to_gm10_gn10 =
          GridwiseContraction::MakeCGridBlockCluster_BlockId_To_GM10_GN10(
              c_grid_desc_gm0_gm1_gn0_gn1);
  
      using CGridBlockCluster_BlockId_To_GM10_GN10 =
          decltype(c_grid_block_cluster_blockid_to_gm10_gn10);
  
      const index_t grid_size = GridwiseContraction::CalculateGridSize(c_grid_desc_gm0_gm1_gn0_gn1);
  
      const bool has_main_k_block_loop = GridwiseContraction::CalculateHasMainKBlockLoop(GK0);
  
      const bool has_double_tail_k_block_loop =
          GridwiseContraction::CalculateHasDoubleTailKBlockLoop(GK0);
  
      {
          std::cout << "a_grid_desc_gk0_gm0_gm10_gm11_gk1{"
                    << a_grid_desc_gk0_gm0_gm10_gm11_gk1.GetLength(I0) << ", "
                    << a_grid_desc_gk0_gm0_gm10_gm11_gk1.GetLength(I1) << ", "
                    << a_grid_desc_gk0_gm0_gm10_gm11_gk1.GetLength(I2) << ", "
                    << a_grid_desc_gk0_gm0_gm10_gm11_gk1.GetLength(I3) << ", "
                    << a_grid_desc_gk0_gm0_gm10_gm11_gk1.GetLength(I4) << "}" << std::endl;
  
          std::cout << "b_grid_desc_gk0_gn0_gn10_gn11_gk1{"
                    << b_grid_desc_gk0_gn0_gn10_gn11_gk1.GetLength(I0) << ", "
                    << b_grid_desc_gk0_gn0_gn10_gn11_gk1.GetLength(I1) << ", "
                    << b_grid_desc_gk0_gn0_gn10_gn11_gk1.GetLength(I2) << ", "
                    << b_grid_desc_gk0_gn0_gn10_gn11_gk1.GetLength(I3) << ", "
                    << b_grid_desc_gk0_gn0_gn10_gn11_gk1.GetLength(I4) << "}" << std::endl;
  
          std::cout << "c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1{ "
                    << c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1.GetLength(I0) << ", "
                    << c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1.GetLength(I1) << ", "
                    << c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1.GetLength(I2) << ", "
                    << c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1.GetLength(I3) << ", "
                    << c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1.GetLength(I4) << ", "
                    << c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1.GetLength(I5) << "}" << std::endl;
      }
  
      float ave_time = 0;
  
      if(has_main_k_block_loop && has_double_tail_k_block_loop)
      {
          const auto kernel = kernel_contraction_dlops_v1r2<
              GridwiseContraction,
              FloatAB,
              FloatC,
              remove_reference_t<AGridDesc_GK0_GM0_GM10_GM11_GK1>,
              remove_reference_t<BGridDesc_GK0_GN0_GN10_GN11_GK1>,
              remove_reference_t<CGridDesc_GM10_BM0_BM1_GN10_BN0_BN1>,
              remove_reference_t<CGridBlockCluster_BlockId_To_GM10_GN10>,
              true,
              true>;
  
          ave_time = launch_and_time_kernel(kernel,
                                            nrepeat,
                                            dim3(grid_size),
                                            dim3(BlockSize),
                                            0,
                                            p_a_grid,
                                            p_b_grid,
                                            p_c_grid,
                                            a_grid_desc_gk0_gm0_gm10_gm11_gk1,
                                            b_grid_desc_gk0_gn0_gn10_gn11_gk1,
                                            c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1,
                                            c_grid_block_cluster_blockid_to_gm10_gn10);
      }
      else if(has_main_k_block_loop && !has_double_tail_k_block_loop)
      {
          const auto kernel = kernel_contraction_dlops_v1r2<
              GridwiseContraction,
              FloatAB,
              FloatC,
              remove_reference_t<AGridDesc_GK0_GM0_GM10_GM11_GK1>,
              remove_reference_t<BGridDesc_GK0_GN0_GN10_GN11_GK1>,
              remove_reference_t<CGridDesc_GM10_BM0_BM1_GN10_BN0_BN1>,
              remove_reference_t<CGridBlockCluster_BlockId_To_GM10_GN10>,
              true,
              false>;
  
          ave_time = launch_and_time_kernel(kernel,
                                            nrepeat,
                                            dim3(grid_size),
                                            dim3(BlockSize),
                                            0,
                                            p_a_grid,
                                            p_b_grid,
                                            p_c_grid,
                                            a_grid_desc_gk0_gm0_gm10_gm11_gk1,
                                            b_grid_desc_gk0_gn0_gn10_gn11_gk1,
                                            c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1,
                                            c_grid_block_cluster_blockid_to_gm10_gn10);
      }
      else if(!has_main_k_block_loop && has_double_tail_k_block_loop)
      {
          const auto kernel = kernel_contraction_dlops_v1r2<
              GridwiseContraction,
              FloatAB,
              FloatC,
              remove_reference_t<AGridDesc_GK0_GM0_GM10_GM11_GK1>,
              remove_reference_t<BGridDesc_GK0_GN0_GN10_GN11_GK1>,
              remove_reference_t<CGridDesc_GM10_BM0_BM1_GN10_BN0_BN1>,
              remove_reference_t<CGridBlockCluster_BlockId_To_GM10_GN10>,
              false,
              true>;
  
          ave_time = launch_and_time_kernel(kernel,
                                            nrepeat,
                                            dim3(grid_size),
                                            dim3(BlockSize),
                                            0,
                                            p_a_grid,
                                            p_b_grid,
                                            p_c_grid,
                                            a_grid_desc_gk0_gm0_gm10_gm11_gk1,
                                            b_grid_desc_gk0_gn0_gn10_gn11_gk1,
                                            c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1,
                                            c_grid_block_cluster_blockid_to_gm10_gn10);
      }
      else
      {
          const auto kernel = kernel_contraction_dlops_v1r2<
              GridwiseContraction,
              FloatAB,
              FloatC,
              remove_reference_t<AGridDesc_GK0_GM0_GM10_GM11_GK1>,
              remove_reference_t<BGridDesc_GK0_GN0_GN10_GN11_GK1>,
              remove_reference_t<CGridDesc_GM10_BM0_BM1_GN10_BN0_BN1>,
              remove_reference_t<CGridBlockCluster_BlockId_To_GM10_GN10>,
              false,
              false>;
  
          ave_time = launch_and_time_kernel(kernel,
                                            nrepeat,
                                            dim3(grid_size),
                                            dim3(BlockSize),
                                            0,
                                            p_a_grid,
                                            p_b_grid,
                                            p_c_grid,
                                            a_grid_desc_gk0_gm0_gm10_gm11_gk1,
                                            b_grid_desc_gk0_gn0_gn10_gn11_gk1,
                                            c_grid_desc_gm10_bm0_bm1_gn10_bn0_bn1,
                                            c_grid_block_cluster_blockid_to_gm10_gn10);
      }
  
      return ave_time;
  }
  #endif
  