/***************************************************************************************************
 * Copyright (c) 2023 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 **************************************************************************************************/


#pragma once

#include "cutlass/arch/barrier.h"
#include "cutlass/pipeline/pipeline.hpp"
#include "cutlass/gemm/kernel/sm100_tile_scheduler.hpp"
#include "cutlass/gemm/kernel/sm90_tile_scheduler_stream_k.hpp"
#include "cutlass/gemm/kernel/tile_scheduler_params.h"
#include "cutlass/conv/detail.hpp"

////////////////////////////////////////////////////////////////////////////////////////////////////

namespace cutlass::gemm::kernel::detail {

// Persistent Thread Block (TB) scheduler leveraging stream-K decomposition
template <
  class TileShape,
  class ClusterShape,
  uint32_t Stages_
>
class PersistentTileSchedulerSm100StreamK {
  using UnderlyingScheduler = PersistentTileSchedulerSm100<ClusterShape, Stages_>;
  using UnderlyingStreamKScheduler = PersistentTileSchedulerSm90StreamK<TileShape, ClusterShape>;
  using InternalWorkTileInfo = typename UnderlyingScheduler::WorkTileInfo;
  using InternalParams = typename UnderlyingScheduler::Params;
  // Shapediv failures currently occur with tile shape N of 192
  static constexpr bool ForceDataParallel = size<1>(TileShape{}) == 192;

public:
  static constexpr uint32_t Stages = Stages_;

  using CLCResponse = typename UnderlyingScheduler::CLCResponse;
  using WorkTileInfo = typename UnderlyingStreamKScheduler::WorkTileInfo;
  using Arguments = typename UnderlyingStreamKScheduler::Arguments;

  using Params = PersistentTileSchedulerSm100StreamKParams;
  using RasterOrder = PersistentTileSchedulerSm90Params::RasterOrder;
  using RasterOrderOptions = PersistentTileSchedulerSm90Params::RasterOrderOptions;

  using SharedStorage = typename UnderlyingScheduler::SharedStorage;
  using Pipeline = typename UnderlyingScheduler::Pipeline;
  using ThrottlePipeline = typename UnderlyingScheduler::ThrottlePipeline;

  static constexpr bool IsDynamicPersistent = true;

  // Number of sub blocks in the kernel epilogue
  static constexpr int EpilogueSubtiles = 1;

  CUTLASS_HOST_DEVICE
  PersistentTileSchedulerSm100StreamK() { }

  CUTLASS_DEVICE
  PersistentTileSchedulerSm100StreamK(Params const& params)
    : sm100_scheduler_(params.sm100_params_)
    , params_(params)
    , block_id_in_cluster_(cute::block_id_in_cluster()) {
    // Set the current linear idx to be equal to the linear idx of the first work tile to be computed
    auto cs = make_shape(
      params.sm100_params_.divmod_cluster_shape_m_.divisor,
      params.sm100_params_.divmod_cluster_shape_n_.divisor,
      Int<1>{});
  }

  CUTLASS_DEVICE
  PersistentTileSchedulerSm100StreamK(CLCResponse* clc_response_ptr, Params const& params, dim3 block_id_in_cluster)
    : sm100_scheduler_(clc_response_ptr, params.sm100_params_, block_id_in_cluster),
      params_(params),
      block_id_in_cluster_(block_id_in_cluster) {
    // Set the current linear idx to be equal to the linear idx of the first work tile to be computed
    auto cs = make_shape(
      params.sm100_params_.divmod_cluster_shape_m_.divisor,
      params.sm100_params_.divmod_cluster_shape_n_.divisor,
      Int<1>{});
  }

  template <class ProblemShape, class TileShapeMNK>
  CUTLASS_DEVICE
  PersistentTileSchedulerSm100StreamK(CLCResponse* clc_response_ptr, Params const& params,
    ProblemShape problem_shape_mnkl, TileShapeMNK tile_shape, dim3 block_id_in_cluster)
    : PersistentTileSchedulerSm100StreamK(clc_response_ptr, params, block_id_in_cluster) { }

  template <class ProblemShape>
  static Params
  to_underlying_arguments(
      ProblemShape problem_shape,
      TileShape tile_shape,
      [[maybe_unused]] ClusterShape cluster_shape,
      KernelHardwareInfo const& hw_info,
      Arguments const& args,
      void* workspace,
      [[maybe_unused]] const uint32_t epilogue_subtile = 1,
      uint32_t ktile_start_alignment_count = 1u) {

    auto cs = cutlass::detail::select_cluster_shape(cluster_shape, hw_info.cluster_shape);
    auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);
    dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape, cs);
    uint32_t k_tile_per_output_tile = cute::size(cute::ceil_div(cute::shape<2>(problem_shape_mnkl), cute::shape<2>(TileShape{})));

    Params params;
    params.initialize(
      problem_blocks,
      k_tile_per_output_tile,
      to_gemm_coord(cs),
      hw_info,
      args.splits,
      args.max_swizzle_size,
      args.raster_order,
      args.reduction_mode,
      ForceDataParallel ? Params::DecompositionMode::DataParallel : args.decomposition_mode,
      workspace,
      ktile_start_alignment_count
    );
    return params;
  }

  template <class ProblemShape, class TileShapeMNK, class AtomThrShape>
  static Params
  to_underlying_arguments(
      ProblemShape problem_shape_mnkl,
      TileShapeMNK tile_shape_mnk,
      AtomThrShape atom_thr_shape_mnk,
      ClusterShape cluster_shape_mnk,
      KernelHardwareInfo const& hw_info,
      Arguments const& args,
      void* workspace = nullptr,
      uint32_t ktile_start_alignment_count = 1u
      ) {

    auto cs = cutlass::detail::select_cluster_shape(cluster_shape_mnk, hw_info.cluster_shape);
    dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape_mnk, atom_thr_shape_mnk, cs);
    uint32_t k_tile_per_output_tile = cute::size(cute::ceil_div(cute::shape<2>(problem_shape_mnkl), cute::shape<2>(TileShape{})));

    Params params;
    params.initialize(
      problem_blocks,
      k_tile_per_output_tile,
      to_gemm_coord(cs),
      hw_info,
      args.splits,
      args.max_swizzle_size,
      args.raster_order,
      args.reduction_mode,
      ForceDataParallel ? Params::DecompositionMode::DataParallel : args.decomposition_mode,
      workspace,
      ktile_start_alignment_count
    );

    return params;
  }

  template <conv::Operator ConvOp, int NumSpatialDims, class TileShapeMNK, class AtomThrShape>
  static Params
  to_underlying_arguments(
      cutlass::conv::ConvProblemShape<ConvOp, NumSpatialDims> problem_shape,
      TileShapeMNK tile_shape_mnk,
      AtomThrShape atom_thr_shape_mnk,
      ClusterShape cluster_shape_mnk,
      KernelHardwareInfo const& hw_info,
      Arguments const& args,
      void* workspace = nullptr
      ) {

    auto problem_shape_mnkl = [&] () {
      // Infer im2col linearization from ConvOp and TileShape
      constexpr bool is_linearized_M = (ConvOp == conv::Operator::kFprop || ConvOp == conv::Operator::kDgrad)
                                        && cute::depth<0>(TileShapeMNK{}) == _0{};
      constexpr bool is_linearized_K = ConvOp == conv::Operator::kWgrad && cute::depth<2>(TileShapeMNK{}) == _1{};
      if constexpr (is_linearized_M || is_linearized_K) {
        // transformation + im2col linearization
        return cutlass::conv::detail::get_linearized_problem_shape_MNKL(problem_shape);
      }
      else {
        // transformation
        return cutlass::conv::detail::get_transformed_problem_shape_MNKL(problem_shape);
      }
    }();

    return to_underlying_arguments(
      problem_shape_mnkl,
      tile_shape_mnk,
      atom_thr_shape_mnk,
      cluster_shape_mnk,
      hw_info,
      args,
      workspace
    );
  }

  static bool
  can_implement(Arguments const& args, KernelHardwareInfo const& hw_info) {
    if (hw_info.cluster_shape.x != hw_info.cluster_shape_fallback.x || 
        hw_info.cluster_shape.y != hw_info.cluster_shape_fallback.y || 
        hw_info.cluster_shape.z != hw_info.cluster_shape_fallback.z) {
      CUTLASS_TRACE_HOST(" CAN IMPLEMENT: Stream-K scheduler requires cluster shape and fallback cluster shape to be the same.\n");
      return false;
    }
    return UnderlyingStreamKScheduler::can_implement(args, hw_info);
  }

  CUTLASS_DEVICE
  PipelineState<Stages> 
  advance_to_next_work(Pipeline& clc_pipeline, PipelineState<Stages> clc_pipe_producer_state) const {
    return sm100_scheduler_.advance_to_next_work(clc_pipeline, clc_pipe_producer_state);
  }

  // Given the inputs, computes the total number of output blocks this problem will compute over
  template<class ProblemShape>
  CUTLASS_HOST_DEVICE
  static dim3
  get_tiled_cta_shape_mnl(ProblemShape problem_shape_mnkl, TileShape blk_shape, ClusterShape cluster_shape) {
    return UnderlyingScheduler::get_tiled_cta_shape_mnl(problem_shape_mnkl, blk_shape, cluster_shape);
  }

  template<class ProblemShape, class TileShapeMNK, class AtomThrShape>
  CUTLASS_HOST_DEVICE
  static dim3
  get_tiled_cta_shape_mnl(ProblemShape problem_shape_mnkl,
                          TileShapeMNK tile_shape_mnk,
                          AtomThrShape atom_thr_shape_mnk,
                          ClusterShape cluster_shape_mnk) {
    return UnderlyingScheduler::get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape_mnk, atom_thr_shape_mnk, cluster_shape_mnk);
  }

  // Given the inputs, computes the physical grid we should launch.
  template <class ProblemShape>
  CUTLASS_HOST_DEVICE
  static dim3
  get_grid_shape(
    Params const& params,
    ProblemShape problem_shape,
    TileShape tile_shape,
    ClusterShape cluster_shape,
    KernelHardwareInfo hw_info,
    [[maybe_unused]] Arguments arguments) {
    
    auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);
    dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape, cluster_shape);
    return params.get_grid_shape(problem_blocks, to_gemm_coord(cluster_shape));
  }

  // Given the inputs, computes the physical grid we should launch.
  template<class ProblemShape, class TileShapeMNK, class AtomThrShape>
  CUTLASS_HOST_DEVICE
  static dim3
  get_grid_shape(
    Params const& params,
    ProblemShape problem_shape_mnkl,
    TileShapeMNK tile_shape_mnk,
    AtomThrShape atom_thr_shape_mnk,
    ClusterShape cluster_shape_mnk,
    KernelHardwareInfo hw_info) {

    dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape_mnk, atom_thr_shape_mnk, cluster_shape_mnk);
    return params.get_grid_shape(problem_blocks, to_gemm_coord(cluster_shape_mnk));
  }


  // Returns the initial work tile info that will be computed over
  CUTLASS_DEVICE
  WorkTileInfo
  initial_work_tile_info(ClusterShape cluster_shape) {
    InternalWorkTileInfo work_tile_info = sm100_scheduler_.initial_work_tile_info(cluster_shape);
    work_tile_info.is_valid_tile = false;
    return convert_work(work_tile_info);
  }

  // Returns a CTA-tiled coordinate for the provided work tile info
  CUTLASS_DEVICE
  auto
  work_tile_to_cta_coord(WorkTileInfo const& work_tile_info) {
    if (is_dp_only()) {
      // For data-parallel decompositions, simply default to the
      // underlying SM100 scheduler.
      auto underlying_work_tile = to_underlying_work_tile_info(work_tile_info);
      return sm100_scheduler_.work_tile_to_cta_coord(underlying_work_tile);
    }
    else {
      // The SM90 stream-K scheduler already operates only at CTA level,
      // so the returned work tile info already contains CTA offsets within
      // each cluster tile.
      return cute::make_coord(
        work_tile_info.M_idx,
        work_tile_info.N_idx,
        _,
        work_tile_info.L_idx
      );
    }
  }

  // Returns whether the current work_tile_info passed in should continue to be used.
  CUTLASS_DEVICE
  bool
  continue_current_work(WorkTileInfo& work_tile_info) const {
    return UnderlyingStreamKScheduler::continue_current_work_for_linear_idx(
      current_work_linear_idx_, unit_iter_start_, block_id_in_cluster_, work_tile_info, params_.sk_params_);
  }

  // Kernel helper function to get next CLC ID and whether to advance the CLC pipeline state.
  template <class CLCPipeline, class CLCPipelineState>
  CUTLASS_DEVICE
  cute::tuple<WorkTileInfo, bool>
  fetch_next_work(
    WorkTileInfo work_tile_info,
    CLCPipeline& clc_pipeline,
    CLCPipelineState clc_pipe_consumer_state) {
    // Check whether we should continue on with the current work unit. If this is the case,
    // the work unit will have been updated in continue_current_work to reflect the new
    // tile to be computed. Return `false` to indicate that the CLC pipeline state
    // need not be advanced.
    if (continue_current_work(work_tile_info)) {
      return cute::make_tuple(work_tile_info, false);
    }

    auto [work_tile, _] = sm100_scheduler_.fetch_next_work(InternalWorkTileInfo{}, clc_pipeline, clc_pipe_consumer_state);
    if (!work_tile.is_valid()) {
      return cute::make_tuple(invalid_work_tile(), true);
    }

    auto converted_work_tile = convert_work(work_tile);

    // Return true to indicate that the CLC pipeline state should be advanced
    return cute::make_tuple(converted_work_tile, true);
  }

  CUTLASS_DEVICE
  cute::tuple<WorkTileInfo, bool>
  fetch_next_work(WorkTileInfo work_tile_info) {
    return cute::make_tuple(work_tile_info, true);
  }

  // Set data SMEM ptr 
  CUTLASS_DEVICE
  void
  set_data_ptr(CLCResponse* clc_response_ptr) {
    sm100_scheduler_.set_data_ptr(clc_response_ptr);
  }

  CUTLASS_DEVICE
  static bool
  valid_warpgroup_in_work_tile(WorkTileInfo const& work_tile_info) {
    return true;
  }

  CUTLASS_DEVICE
  static bool
  requires_separate_reduction(Params const& params) {
    return false;
  }

  // Returns whether the block assigned this work should compute the epilogue for the corresponding
  // output tile. For the case of stream-K, this should only occur if the work is marked as the final split.
  CUTLASS_HOST_DEVICE
  static bool
  compute_epilogue(WorkTileInfo const& work_tile_info, Params const& params) {
    return UnderlyingStreamKScheduler::compute_epilogue(work_tile_info, params.sk_params_);
  }

  // Non-static variant of compute_epilogue. Used in cases where passing
  // in Params is inconvenient.
  CUTLASS_HOST_DEVICE
  bool
  compute_epilogue(WorkTileInfo const& work_tile_info) const {
    return UnderlyingStreamKScheduler::compute_epilogue(work_tile_info, params_.sk_params_);
  }

  template <class ProblemShape, class ElementAccumulator>
  static size_t
  get_workspace_size(
    Arguments const& args,
    ProblemShape problem_shape,
    KernelHardwareInfo const& hw_info,
    uint32_t reduction_warp_groups,
    [[maybe_unused]] const uint32_t epilogue_subtile = 1,
    uint32_t num_accumulator_mtxs = 1,
    uint32_t ktile_start_alignment_count = 1) {

    auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);

    auto cs = cutlass::detail::select_cluster_shape(ClusterShape{}, hw_info.cluster_shape);
    TileShape tile_shape;

    dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape, cs);
    uint32_t k_tile_per_output_tile = cute::size(cute::ceil_div(cute::shape<2>(problem_shape_mnkl), cute::shape<2>(TileShape{})));

    return Params::get_workspace_size(
      problem_blocks,
      k_tile_per_output_tile,
      to_gemm_coord(tile_shape),
      to_gemm_coord(cs),
      hw_info,
      args.splits,
      args.max_swizzle_size,
      args.raster_order,
      ForceDataParallel ? Params::DecompositionMode::DataParallel : args.decomposition_mode,
      args.reduction_mode,
      reduction_warp_groups,
      sizeof_bits<typename UnderlyingStreamKScheduler::BarrierType>::value,
      sizeof_bits<ElementAccumulator>::value,
      EpilogueSubtiles,
      num_accumulator_mtxs,
      ktile_start_alignment_count
    );
  }

  template <class ElementAccumulator, class ProblemShape, class TileShapeMNK, class AtomThrShape>
  static size_t
  get_workspace_size(
      Arguments const& args,
      ProblemShape problem_shape,
      TileShapeMNK tile_shape_mnk,
      AtomThrShape atom_thr_shape_mnk,
      ClusterShape cluster_shape_mnk,
      KernelHardwareInfo const& hw_info,
      uint32_t reduction_warp_groups,
      uint32_t num_accumulator_mtxs = 1,
      uint32_t ktile_start_alignment_count = 1) {

    auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);

    auto cs = cutlass::detail::select_cluster_shape(cluster_shape_mnk, hw_info.cluster_shape);

    dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape_mnk, atom_thr_shape_mnk, cs);
    uint32_t k_tile_per_output_tile = cute::size(cute::ceil_div(cute::shape<2>(problem_shape_mnkl), cute::shape<2>(TileShape{})));

    auto cta_tile_shape_mnk = shape_div(tile_shape_mnk, atom_thr_shape_mnk);

    return Params::get_workspace_size(
      problem_blocks,
      k_tile_per_output_tile,
      to_gemm_coord(cta_tile_shape_mnk),
      to_gemm_coord(cs),
      hw_info,
      args.splits,
      args.max_swizzle_size,
      args.raster_order,
      ForceDataParallel ? Params::DecompositionMode::DataParallel : args.decomposition_mode,
      args.reduction_mode,
      reduction_warp_groups,
      sizeof_bits<typename UnderlyingStreamKScheduler::BarrierType>::value,
      sizeof_bits<ElementAccumulator>::value,
      EpilogueSubtiles,
      num_accumulator_mtxs,
      ktile_start_alignment_count
    );
  }

  template <class ProblemShape, class ElementAccumulator>
  static cutlass::Status
  initialize_workspace(
    Arguments const& args,
    void* workspace,
    cudaStream_t stream,
    ProblemShape const& problem_shape,
    KernelHardwareInfo const& hw_info,
    uint32_t reduction_warp_groups,
    [[maybe_unused]] const uint32_t epilogue_subtile = 1,
    uint32_t num_accumulator_mtxs = 1,
    CudaHostAdapter *cuda_adapter = nullptr,
    uint32_t ktile_start_alignment_count = 1) {

    auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);

    auto cs = cutlass::detail::select_cluster_shape(ClusterShape{}, hw_info.cluster_shape);
    TileShape tile_shape;

    dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape, cs);
    uint32_t k_tile_per_output_tile = cute::size(cute::ceil_div(cute::shape<2>(problem_shape_mnkl), cute::shape<2>(TileShape{})));

    return Params::initialize_workspace(
      workspace,
      stream,
      problem_blocks,
      k_tile_per_output_tile,
      to_gemm_coord(tile_shape),
      to_gemm_coord(cs),
      hw_info,
      args.splits,
      args.max_swizzle_size,
      args.raster_order,
      ForceDataParallel ? Params::DecompositionMode::DataParallel : args.decomposition_mode,
      args.reduction_mode,
      reduction_warp_groups,
      sizeof_bits<typename UnderlyingStreamKScheduler::BarrierType>::value,
      sizeof_bits<ElementAccumulator>::value,
      EpilogueSubtiles,
      num_accumulator_mtxs,
      cuda_adapter,
      ktile_start_alignment_count
    );
  }

  template <class ElementAccumulator, class ProblemShape, class TileShapeMNK, class AtomThrShape>
  static cutlass::Status
  initialize_workspace(
      Arguments const& args,
      void* workspace,
      cudaStream_t stream,
      ProblemShape const& problem_shape,
      TileShapeMNK tile_shape_mnk,
      AtomThrShape atom_thr_shape_mnk,
      ClusterShape cluster_shape_mnk,
      KernelHardwareInfo const& hw_info,
      uint32_t reduction_warp_groups,
      uint32_t num_accumulator_mtxs = 1,
      CudaHostAdapter *cuda_adapter = nullptr,
      uint32_t ktile_start_alignment_count = 1) {

    auto problem_shape_mnkl = cute::append<4>(problem_shape, 1);

    auto cs = cutlass::detail::select_cluster_shape(cluster_shape_mnk, hw_info.cluster_shape);

    dim3 problem_blocks = get_tiled_cta_shape_mnl(problem_shape_mnkl, tile_shape_mnk, atom_thr_shape_mnk, cs);
    uint32_t k_tile_per_output_tile = cute::size(cute::ceil_div(cute::shape<2>(problem_shape_mnkl), cute::shape<2>(TileShape{})));

    auto cta_tile_shape_mnk = shape_div(tile_shape_mnk, atom_thr_shape_mnk);

    return Params::initialize_workspace(
      workspace,
      stream,
      problem_blocks,
      k_tile_per_output_tile,
      to_gemm_coord(cta_tile_shape_mnk),
      to_gemm_coord(cs),
      hw_info,
      args.splits,
      args.max_swizzle_size,
      args.raster_order,
      ForceDataParallel ? Params::DecompositionMode::DataParallel : args.decomposition_mode,
      args.reduction_mode,
      reduction_warp_groups,
      sizeof_bits<typename UnderlyingStreamKScheduler::BarrierType>::value,
      sizeof_bits<ElementAccumulator>::value,
      EpilogueSubtiles,
      num_accumulator_mtxs,
      cuda_adapter,
      ktile_start_alignment_count
    );
  }

  template <class ProblemShape, class TileShapeMNK>
  CUTLASS_HOST_DEVICE
  static int
  get_work_k_tile_count(WorkTileInfo const& work_tile_info, ProblemShape, TileShapeMNK) {
    return work_tile_info.k_tile_count;
  }

  CUTLASS_HOST_DEVICE
  static uint32_t
  get_work_k_tile_start(WorkTileInfo const& work_tile_info) {
    return work_tile_info.K_idx;
  }

  template <class ProblemShape, class TileShapeMNK, class Shape>
  CUTLASS_DEVICE
  auto
  get_k_tile_iterator(WorkTileInfo const& work_tile_info, ProblemShape problem_shape, TileShapeMNK tile_shape, Shape) {
    // Get the shape of k tiles instead of the counter.  Otherwise, if the problem shape has
    // multiple k modes, the DMA loop would need to decompose the iterator onto every mode
    // every time global loading happens.  This would incur extra overhead.
    auto k_tiles = cute::ceil_div(cute::get<2>(problem_shape), cute::get<2>(tile_shape));
    auto k_tile_start = get_work_k_tile_start(work_tile_info);
    // Iterate start from current k tile start over the k tiles shape.
    return cute::make_coord_iterator(idx2crd(k_tile_start, k_tiles), k_tiles);
  }

  // Returns whether fixup is needed for `work_tile_info`.
  CUTLASS_HOST_DEVICE
  bool
  requires_fixup(WorkTileInfo const work_tile_info) const {
    return UnderlyingStreamKScheduler::requires_fixup(params_.sk_params_, work_tile_info);
  }

  // Performs the reduction across splits for a given output tile.
  template <class FrgTensorC>
  CUTLASS_DEVICE
  void
  fixup(
    WorkTileInfo const& work_tile_info,
    FrgTensorC& accumulators,
    uint32_t num_barriers,
    uint32_t barrier_idx,
    uint32_t num_accumulator_mtxs = 1) const {

    using BarrierManager = SyncManager<cutlass::detail::SyncwarpSync, NumThreadsPerWarp>;

    UnderlyingStreamKScheduler s;
    return s.template fixup_helper<FrgTensorC, BarrierManager>(
      params_.sk_params_, work_tile_info, accumulators, num_barriers, barrier_idx, num_accumulator_mtxs);
  }


  // Performs the reduction across splits for a given output tile.
  template <class FrgTensorC>
  CUTLASS_DEVICE
  static void
  fixup(
      Params const& params,
      WorkTileInfo const& work_tile_info,
      FrgTensorC& accumulators,
      uint32_t num_barriers,
      uint32_t barrier_idx) {
    UnderlyingStreamKScheduler::fixup(params.sk_params_, work_tile_info, accumulators, num_barriers, barrier_idx);
  }

  // Performs reduction across splits for a given output tile
  template <
    bool IsComplex,
    class TiledMma,
    class AccEngine,
    class AccLayout,
    class AccumulatorPipeline,
    class AccumulatorPipelineState,
    class CopyOpT2R
  >
  CUTLASS_DEVICE
  AccumulatorPipelineState
  fixup(
      TiledMma const& tiled_mma,
      WorkTileInfo const& work_tile_info,
      cute::Tensor<AccEngine, AccLayout>& accumulators,
      AccumulatorPipeline acc_pipeline,
      AccumulatorPipelineState acc_pipe_consumer_state,
      CopyOpT2R) const {
    using namespace cute;
    static_assert(cute::is_rmem_v<AccEngine> || cute::is_tmem_v<AccEngine>, "Accumulator must be in either TMEM or RF");

    if constexpr (ForceDataParallel) {
      return acc_pipe_consumer_state;
    }
    else {
      if (!requires_fixup(work_tile_info)) {
        if constexpr (cute::is_tmem_v<AccEngine>) {
          if (!work_tile_info.is_valid()) {
            // The first work tile can be invalid, but still must release TMEM
            acc_pipeline.consumer_wait(acc_pipe_consumer_state);
            acc_pipeline.consumer_release(acc_pipe_consumer_state);
            ++acc_pipe_consumer_state;
          }
        }
        return acc_pipe_consumer_state;
      }

      if constexpr (cute::is_tmem_v<AccEngine>) {
        // When accumulators reside in TMEM, perform TMEM -> RF loads before performing fixup,
        // and perform RF -> TMEM stores after fixup (when the split must compute the epilogue)
        if constexpr (IsComplex) {
          constexpr uint32_t NumAccumulatorMtx = 2;
          Tensor accumulators_real = accumulators(_,_,_,0);
          tmem_fixup(
            tiled_mma,
            work_tile_info,
            accumulators_real,
            acc_pipeline,
            acc_pipe_consumer_state,
            CopyOpT2R{},
            NumAccumulatorMtx,
            0 /*idx_accumulator_mtx*/
          );

          Tensor accumulators_imag = accumulators(_,_,_,1);
          return tmem_fixup(
            tiled_mma,
            work_tile_info,
            accumulators_imag,
            acc_pipeline,
            acc_pipe_consumer_state,
            CopyOpT2R{},
            NumAccumulatorMtx,
            1 /*idx_accumulator_mtx*/
          );
        }
        else {
          return tmem_fixup(
            tiled_mma,
            work_tile_info,
            accumulators,
            acc_pipeline,
            acc_pipe_consumer_state,
            CopyOpT2R{}
          );
        }
      }
      else {
        // Simply perform fixup without TMEM loads when accumulators reside in RF
        constexpr uint32_t ThreadsForFixup = NumThreadsPerWarpGroup;
        constexpr uint32_t Offset = static_cast<int>(cutlass::arch::ReservedNamedBarriers::StreamkBarrier0);
        constexpr uint32_t MaxNumNamedBarriers = 1;
        constexpr uint32_t BarrierIdx = 0;
        using BarrierManager = NamedBarrierManager<ThreadsForFixup, Offset, MaxNumNamedBarriers>;
        constexpr int NumAccumulatorMtx = IsComplex ? 2 : 1;

        UnderlyingStreamKScheduler::template fixup_helper<cute::remove_cvref_t<decltype(accumulators)>, BarrierManager>(
          params_.sk_params_, work_tile_info, accumulators, MaxNumNamedBarriers, BarrierIdx, NumAccumulatorMtx);
        return acc_pipe_consumer_state;
      }
    }
  }

  // Convert CTA-level work tile info to cluster-level tile coord
  CUTLASS_DEVICE
  auto
  work_tile_to_cluster_coord_mnkl(WorkTileInfo work_tile_info) const {
    typename UnderlyingScheduler::WorkTileInfo tmp{
      work_tile_info.M_idx,
      work_tile_info.N_idx,
      work_tile_info.L_idx,
      work_tile_info.is_valid()
    };
    return sm100_scheduler_.work_tile_to_cluster_coord_mnkl(tmp);
  }

private:
  CUTLASS_HOST_DEVICE
  WorkTileInfo invalid_work_tile() const {
    // Mark the work tile as invalid based on its having a 0 K tiles to comptue.
    // Set the M, N, and L indices to be outside of the range of valid tiles for the problem.
    return {
      static_cast<int32_t>(params_.sm100_params_.problem_tiles_m_) * params_.sm100_params_.divmod_cluster_shape_m_.divisor,
      static_cast<int32_t>(params_.sm100_params_.problem_tiles_n_) * params_.sm100_params_.divmod_cluster_shape_n_.divisor,
      0, // K_idx
      static_cast<int32_t>(params_.sm100_params_.problem_tiles_l_),
      0  // k_tile_count
    };
  }

  // Converts the work tile info returned by the SM100 scheduler to a linear index
  CUTLASS_DEVICE
  uint64_t
  to_linear_idx(
    InternalWorkTileInfo const& work_tile_info,
    Params const& params) {
    // The InternalWorkTileInfo returned from CLC query gives all CTAs in a cluster
    // the tile offset corresponding to the first CTA tile in the cluster tile assigned
    // to the cluster. Since the SM90 tile scheduler operates at CTA level, we must assign
    // each CTA its own tile when computing the linear ID to be used by the SM90
    // stream-K scheduler.
    auto start_cta_m_preferred_cluster = params.sk_params_.truncate_to_cluster_size_m(work_tile_info.M_idx);
    auto start_cta_n_preferred_cluster = params.sk_params_.truncate_to_cluster_size_n(work_tile_info.N_idx);
    uint64_t cluster_idx = gridDim.y * start_cta_m_preferred_cluster + start_cta_n_preferred_cluster;
    uint64_t sm_count = gridDim.x * gridDim.y;
    uint64_t wave_idx = work_tile_info.L_idx;

    auto cluster_start_linear_id = sm_count * wave_idx + cluster_idx;

    // Determine the offset of this CTA in the preferred cluster shape.
    // This calculation aims to accommodate both cases in which this CTA is part of a preferred cluster
    // and those in which it is part of a fallback cluster.
    //
    // The calculation is performed by computing the starting M and N index of the preferred cluster that
    // this CTA would be in, and then subtracting these from the true CTA M and N indexes.
    //
    // In the case where this CTA is part of a preferred cluster, the resulting offsets are equivalent
    // to those returned by cute::block_id_in_cluster();
    uint64_t cta_m_in_preferred_cluster = work_tile_info.M_idx - start_cta_m_preferred_cluster;
    uint64_t cta_n_in_preferred_cluster = work_tile_info.N_idx - start_cta_n_preferred_cluster;

    if (params.sk_params_.raster_order_ == RasterOrder::AlongN) {
      return cluster_start_linear_id + (params.sk_params_.divmod_cluster_shape_minor_.divisor * cta_n_in_preferred_cluster) + cta_m_in_preferred_cluster;
    }
    else {
      return cluster_start_linear_id + (params.sk_params_.divmod_cluster_shape_minor_.divisor * cta_m_in_preferred_cluster) + cta_n_in_preferred_cluster;
    }
  }

  // Converts the work tile info returned by the SM100 scheduler to a stream-K work tile info
  CUTLASS_DEVICE
  WorkTileInfo
  convert_work(InternalWorkTileInfo const& work_tile_info) {
    if (has_sk_work()) {
      current_work_linear_idx_ = to_linear_idx(work_tile_info, params_);
      auto work = UnderlyingStreamKScheduler::get_current_work_for_linear_idx(unit_iter_start_, current_work_linear_idx_, block_id_in_cluster_, params_.sk_params_);
      if (!work.is_valid()) {
        return invalid_work_tile();
      }
      return work;
    }
    else if (is_split_k()) {
      // Split-K offsets are returned directly by CLC query (rather than being
      // returned by the SM90 stream-K tile scheduler). CLC query returns
      // the first CTA tile of work for each CTA in a cluster, but later use of the
      // split-K work tile for fixup expect a CTA-offset tile. Thus, we need to offset
      // each CTA's M and N index by the CTA offset in the cluster.
      int32_t M_idx = work_tile_info.M_idx;
      int32_t N_idx = work_tile_info.N_idx;

      int L_idx, Split_idx;
      params_.sk_params_.divmod_splits_(L_idx, Split_idx, work_tile_info.L_idx);

      int additional_k_tiles = 0;
      int split_start_offset = params_.sk_params_.big_units_;

      if (Split_idx < params_.sk_params_.big_units_) {
        // Offsets for "big" units. One additional k iteration is performed,
        // and each split preceding us was a big unit, so we must increase
        // our split starting offset by our split ID (Split_idx).
        additional_k_tiles = 1;
        split_start_offset = Split_idx;
      }

      // Set up k iteration count and split starting iteration assuming the
      // iteration space is evenly split.
      uint32_t k_tiles = params_.sk_params_.divmod_k_tiles_per_sk_unit_.divisor;
      uint32_t K_idx = Split_idx * k_tiles;

      // Apply any fixup needed to handle residuals
      K_idx += split_start_offset;
      k_tiles += additional_k_tiles;

      // K_idx is even for each cta.
      //
      // * Example
      // 53 k_tiles per output tile
      // 10 k_tiles for normal size split
      // 11 k_tiles for start three big unit
      //
      // split 0 : K_idx = [0,  10], k_tiles = 11 -> K_idx = [0,  11], k_tiles = 12
      // split 1 : K_idx = [11, 21], k_tiles = 11 -> K_idx = [12, 21], k_tiles = 10
      // split 2 : K_idx = [22, 32], k_tiles = 11 -> K_idx = [22, 33], k_tiles = 12
      // split 3 : K_idx = [33, 42], k_tiles = 10 -> K_idx = [34, 42], k_tiles = 9 -> K_idx = [34, 43], k_tiles = 10
      // split 4 : K_idx = [43, 52], k_tiles = 10 -> K_idx = [44, 52], k_tiles = 9
      if (params_.sk_params_.ktile_start_alignment_count_ == 2u && K_idx % 2 != 0) {
        // If current cta K_idx not start from even, give up one k_tile
        K_idx += 1;
        k_tiles -= 1;
      }
      if (params_.sk_params_.ktile_start_alignment_count_ == 2u &&
          (K_idx + k_tiles) % 2 != 0 &&
          (K_idx + k_tiles) < params_.sk_params_.divmod_tiles_per_output_tile_.divisor) {
        // If next cta K_idx not start from even, acquire one k_tile
        k_tiles += 1;
      }

      return {
        M_idx,
        N_idx,
        static_cast<int32_t>(K_idx),
        static_cast<int32_t>(L_idx),
        k_tiles,
        k_tiles  // remaining iterations
      };
    }
    else {
      // Data-parallel case
      return {
        static_cast<int32_t>(work_tile_info.M_idx),
        static_cast<int32_t>(work_tile_info.N_idx),
        static_cast<int32_t>(0),                   // K_idx
        static_cast<int32_t>(work_tile_info.L_idx),
        static_cast<uint32_t>(params_.sk_params_.divmod_tiles_per_output_tile_.divisor),
        static_cast<uint32_t>(params_.sk_params_.divmod_tiles_per_output_tile_.divisor)
      };
    }
  }

  // Converts a WorkTileInfo struct to the WorkTileInfo representation
  // of the underlying SM100 scheduler.
  CUTLASS_HOST_DEVICE static
  InternalWorkTileInfo
  to_underlying_work_tile_info(WorkTileInfo const& work_tile_info) {
    return {
      work_tile_info.M_idx,
      work_tile_info.N_idx,
      work_tile_info.L_idx,
      work_tile_info.is_valid()
    };
  }

  // Returns whether the current parameters contain only data-parallel tiles
  CUTLASS_HOST_DEVICE
  bool
  is_dp_only() const {
    return params_.sk_params_.sk_units_ == 0 && params_.sk_params_.divmod_splits_.divisor == 1;
  }

  // Returns whether the current parameters are for a split-K decomposition
  CUTLASS_HOST_DEVICE
  bool
  is_split_k() const {
    return params_.sk_params_.divmod_splits_.divisor > 1;
  }

  // Returns whether the current parameters contain any stream-K work
  CUTLASS_HOST_DEVICE
  bool
  has_sk_work() const {
    return params_.sk_params_.sk_units_ > 0;
  }

  // Performs reduction across splits for a given output tile
  template <
    class TiledMma,
    class AccEngine,
    class AccLayout,
    class AccumulatorPipeline,
    class AccumulatorPipelineState,
    class CopyOpT2R
  >
  CUTLASS_DEVICE
  AccumulatorPipelineState
  tmem_fixup(
      TiledMma const& tiled_mma,
      WorkTileInfo const& work_tile_info,
      cute::Tensor<AccEngine, AccLayout>& accumulators,
      AccumulatorPipeline acc_pipeline,
      AccumulatorPipelineState acc_pipe_consumer_state,
      CopyOpT2R,
      uint32_t num_accumulator_mtx = 1,
      uint32_t idx_accumulator_mtx = 0) const {
    using namespace cute;
    static_assert(cute::is_tmem_v<AccEngine>, "Accumulator must be in TMEM");

    using ElementAccumulator = typename AccEngine::element_type;

    constexpr uint32_t ThreadsForFixup = NumThreadsPerWarpGroup;
    constexpr uint32_t Offset = static_cast<int>(cutlass::arch::ReservedNamedBarriers::StreamkBarrier0);
    constexpr uint32_t MaxNumNamedBarriers = 1;
    constexpr uint32_t BarrierIdx = 0;
    using BarrierManager = NamedBarrierManager<ThreadsForFixup, Offset, MaxNumNamedBarriers>;

    // When accumulators reside in TMEM, perform TMEM -> RF loads before performing fixup,
    // and perform RF -> TMEM stores after fixup (when the split must compute the epilogue)
    auto dummy_gmem_workspace = make_tensor(
      make_gmem_ptr<ElementAccumulator>(nullptr),
      make_layout(take<0,2>(TileShape{}), GenRowMajor{})); // (TILE_M,TILE_N)

    auto dummy_gmem_buffer = tiled_mma.get_slice(0).partition_C(dummy_gmem_workspace); // (MMA,MMA_M,MMA_N)

    auto tmem_load = make_tmem_copy(CopyOpT2R{}, accumulators);
    auto tmem_store = make_tmem_copy(cute::TMEM::tmem_load_to_store(CopyOpT2R{}), accumulators);

    auto thr_tmem_load = tmem_load.get_slice(threadIdx.x % ThreadsForFixup);
    auto thr_tmem_store = tmem_store.get_slice(threadIdx.x % ThreadsForFixup);

    Tensor tCtAcc = thr_tmem_load.partition_S(accumulators);      // (TMEM_LOAD,TMEM_LOAD_MMA,TMEM_LOAD_M,TMEM_LOAD_N)
    Tensor tCgAcc = thr_tmem_load.partition_D(dummy_gmem_buffer); // (TMEM_LOAD,TMEM_LOAD_MMA,TMEM_LOAD_M,TMEM_LOAD_N)
    auto tCrAcc = make_tensor<ElementAccumulator>(shape(tCgAcc)); // (TMEM_LOAD,TMEM_LOAD_MMA,TMEM_LOAD_M,TMEM_LOAD_N)

    acc_pipeline.consumer_wait(acc_pipe_consumer_state);

    // Copy accumulators from tmem to rmem for reduction
    copy(tmem_load, tCtAcc, tCrAcc);

    bool should_compute_epilogue = compute_epilogue(work_tile_info);
    if (!should_compute_epilogue && (idx_accumulator_mtx == (num_accumulator_mtx - 1))) {
      // Splits that do not compute the epilogue must advance the accumulator pipeline
      cutlass::arch::fence_view_async_tmem_load();
      acc_pipeline.consumer_release(acc_pipe_consumer_state);
      ++acc_pipe_consumer_state;
    }

    // Perform fixup
    UnderlyingStreamKScheduler::template fixup_helper<decltype(tCrAcc), BarrierManager>(
      params_.sk_params_, work_tile_info, tCrAcc, MaxNumNamedBarriers, BarrierIdx, num_accumulator_mtx, idx_accumulator_mtx);

    if (should_compute_epilogue) {
      // Splits that compute the epilogue copy the reduced accumulators back to tmem for
      // the epilogue to compute on it
      copy(tmem_store, tCrAcc, tCtAcc);
    }

    return acc_pipe_consumer_state;
  }


  //
  // Members
  //

  UnderlyingScheduler sm100_scheduler_;
  Params params_;
  dim3 block_id_in_cluster_;
  uint64_t current_work_linear_idx_ = 0;
  uint32_t unit_iter_start_ = 0;

  // This might not be needed
  bool is_fallback_cluster_ = false;
};

///////////////////////////////////////////////////////////////////////////////

} // end namespace cutlass::gemm::kernel::detail
