/*
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright contributors to the vLLM project
 *
 * MXFP4 x MXFP4 block-scaled grouped GEMM kernel for MoE on SM100.
 * Uses Cutlass mx_float4_t operands, E8M0 block scales, and 32-element groups.
 */

#include <torch/csrc/stable/library.h>
#include <torch/csrc/stable/tensor.h>
#include "libtorch_stable/torch_utils.h"

#include <cutlass/arch/arch.h>

#include "cutlass_extensions/common.hpp"

#include "cute/tensor.hpp"
#include "cutlass/tensor_ref.h"
#include "cutlass/epilogue/collective/default_epilogue.hpp"
#include "cutlass/epilogue/thread/linear_combination.h"
#include "cutlass/gemm/dispatch_policy.hpp"
#include "cutlass/gemm/group_array_problem_shape.hpp"
#include "cutlass/gemm/collective/collective_builder.hpp"
#include "cutlass/epilogue/collective/collective_builder.hpp"
#include "cutlass/gemm/device/gemm_universal_adapter.h"
#include "cutlass/gemm/kernel/gemm_universal.hpp"

#include "cutlass/util/packed_stride.hpp"
#include <cassert>

using namespace cute;

// Offset-computation kernel for MXFP4 grouped GEMM (group size 32).
template <typename ElementAB, typename ElementC, typename ElementSF,
          typename LayoutSFA, typename LayoutSFB, typename ScaleConfig>
__global__ void __mxfp4_get_group_gemm_starts(
    ElementAB** a_offsets, ElementAB** b_offsets, ElementC** out_offsets,
    ElementSF** a_scales_offsets, ElementSF** b_scales_offsets,
    LayoutSFA* layout_sfa_base_as_int, LayoutSFB* layout_sfb_base_as_int,
    ElementAB* a_base_as_int, ElementAB* b_base_as_int,
    ElementC* out_base_as_int, ElementSF* a_scales_base_as_int,
    ElementSF* b_scales_base_as_int, const int32_t* expert_offsets,
    const int32_t* sf_offsets, const int32_t* problem_sizes_as_shapes,
    int64_t* a_strides, int64_t* b_strides, int64_t* c_strides,
    const int64_t a_stride_val, const int64_t b_stride_val,
    const int64_t c_stride_val, const int K, const int N) {
  int64_t expert_id = threadIdx.x;
  if (expert_id >= gridDim.x * blockDim.x) {
    return;
  }
  int64_t expert_offset = static_cast<int64_t>(expert_offsets[expert_id]);
  int64_t sf_offset = static_cast<int64_t>(sf_offsets[expert_id]);
  int64_t group_size = 32;
  int64_t m = static_cast<int64_t>(problem_sizes_as_shapes[expert_id * 3]);
  int64_t n = static_cast<int64_t>(problem_sizes_as_shapes[expert_id * 3 + 1]);
  int64_t k = static_cast<int64_t>(problem_sizes_as_shapes[expert_id * 3 + 2]);
  assert((m >= 0 && n == N && k == K && k % 2 == 0) &&
         "unexpected problem sizes");

  int64_t half_k = static_cast<int64_t>(k / 2);
  int64_t group_k = static_cast<int64_t>(k / group_size);
  // Shape of A as uint8/byte = [M, K // 2]
  a_offsets[expert_id] = a_base_as_int + expert_offset * half_k;
  // Shape of B as uint8/byte = [E, N, K // 2]
  b_offsets[expert_id] = b_base_as_int + expert_id * n * half_k;
  // Shape of C = [M, N]
  out_offsets[expert_id] = out_base_as_int + expert_offset * n;
  // Shape of a_scale = [sum(sf_sizes), K // group_size]
  a_scales_offsets[expert_id] = a_scales_base_as_int + sf_offset * group_k;

  assert((reinterpret_cast<uintptr_t>(a_scales_offsets[expert_id]) % 128) ==
             0 &&
         "TMA requires 128-byte alignment");

  // Shape of B scale = [E, N, K // group_size]
  b_scales_offsets[expert_id] = b_scales_base_as_int + expert_id * n * group_k;
  assert((reinterpret_cast<uintptr_t>(b_scales_offsets[expert_id]) % 128) ==
             0 &&
         "TMA requires 128-byte alignment");

  // Initialize strides
  a_strides[expert_id] = a_stride_val;
  b_strides[expert_id] = b_stride_val;
  c_strides[expert_id] = c_stride_val;

  LayoutSFA* layout_sfa_ptr = layout_sfa_base_as_int + expert_id;
  LayoutSFB* layout_sfb_ptr = layout_sfb_base_as_int + expert_id;

  *layout_sfa_ptr = ScaleConfig::tile_atom_to_shape_SFA(cute::make_shape(
      static_cast<int>(m), static_cast<int>(n), static_cast<int>(k), 1));
  *layout_sfb_ptr = ScaleConfig::tile_atom_to_shape_SFB(cute::make_shape(
      static_cast<int>(m), static_cast<int>(n), static_cast<int>(k), 1));
}

#define __CALL_MXFP4_GET_STARTS_KERNEL(ELEMENT_AB_TYPE, SF_TYPE,               \
                                       TENSOR_C_TYPE, C_TYPE, LayoutSFA,       \
                                       LayoutSFB, ScaleConfig)                 \
  else if (out_tensors.scalar_type() == TENSOR_C_TYPE) {                       \
    __mxfp4_get_group_gemm_starts<ELEMENT_AB_TYPE, C_TYPE, SF_TYPE, LayoutSFA, \
                                  LayoutSFB, ScaleConfig>                      \
        <<<1, num_experts, 0, stream>>>(                                       \
            static_cast<ELEMENT_AB_TYPE**>(a_starts.data_ptr()),               \
            static_cast<ELEMENT_AB_TYPE**>(b_starts.data_ptr()),               \
            static_cast<C_TYPE**>(out_starts.data_ptr()),                      \
            static_cast<SF_TYPE**>(a_scales_starts.data_ptr()),                \
            static_cast<SF_TYPE**>(b_scales_starts.data_ptr()),                \
            reinterpret_cast<LayoutSFA*>(layout_sfa.data_ptr()),               \
            reinterpret_cast<LayoutSFB*>(layout_sfb.data_ptr()),               \
            static_cast<ELEMENT_AB_TYPE*>(a_tensors.data_ptr()),               \
            static_cast<ELEMENT_AB_TYPE*>(b_tensors.data_ptr()),               \
            static_cast<C_TYPE*>(out_tensors.data_ptr()),                      \
            static_cast<SF_TYPE*>(a_scales.data_ptr()),                        \
            static_cast<SF_TYPE*>(b_scales.data_ptr()),                        \
            static_cast<int32_t*>(expert_offsets.data_ptr()),                  \
            static_cast<int32_t*>(sf_offsets.data_ptr()),                      \
            static_cast<int32_t*>(problem_sizes.data_ptr()),                   \
            static_cast<int64_t*>(a_strides.data_ptr()),                       \
            static_cast<int64_t*>(b_strides.data_ptr()),                       \
            static_cast<int64_t*>(c_strides.data_ptr()), a_stride_val,         \
            b_stride_val, c_stride_val, K, N);                                 \
  }

template <typename LayoutSFA, typename LayoutSFB, typename ScaleConfig>
void mxfp4_run_get_group_gemm_starts(
    const torch::stable::Tensor& a_starts,
    const torch::stable::Tensor& b_starts,
    const torch::stable::Tensor& out_starts,
    const torch::stable::Tensor& a_scales_starts,
    const torch::stable::Tensor& b_scales_starts,
    const torch::stable::Tensor& layout_sfa,
    const torch::stable::Tensor& layout_sfb,
    const torch::stable::Tensor& a_strides,
    const torch::stable::Tensor& b_strides,
    const torch::stable::Tensor& c_strides, int64_t a_stride_val,
    int64_t b_stride_val, int64_t c_stride_val,
    torch::stable::Tensor const& a_tensors,
    torch::stable::Tensor const& b_tensors,
    torch::stable::Tensor const& out_tensors,
    torch::stable::Tensor const& a_scales,
    torch::stable::Tensor const& b_scales,
    torch::stable::Tensor const& expert_offsets,
    torch::stable::Tensor const& sf_offsets,
    torch::stable::Tensor const& problem_sizes, int M, int N, int K) {
  int num_experts = (int)expert_offsets.size(0);
  auto stream = get_current_cuda_stream(a_tensors.get_device_index());

  STD_TORCH_CHECK(out_tensors.size(1) == N,
                  "Output tensor shape doesn't match expected shape");
  STD_TORCH_CHECK(K / 2 == b_tensors.size(2),
                  "b_tensors(dim = 2) and a_tensors(dim = 1) trailing"
                  " dimension must match");
  if (false) {
  }
  // MXFP4 uses E8M0 (float_ue8m0_t) scale factors
  __CALL_MXFP4_GET_STARTS_KERNEL(cutlass::float_e2m1_t, cutlass::float_ue8m0_t,
                                 torch::headeronly::ScalarType::BFloat16,
                                 cutlass::bfloat16_t, LayoutSFA, LayoutSFB,
                                 ScaleConfig)
  __CALL_MXFP4_GET_STARTS_KERNEL(cutlass::float_e2m1_t, cutlass::float_ue8m0_t,
                                 torch::headeronly::ScalarType::Half, half,
                                 LayoutSFA, LayoutSFB, ScaleConfig)
  else {
    STD_TORCH_CHECK(false, "Invalid output type (must be float16 or bfloat16)");
  }
}

template <typename OutType>
void run_mxfp4_blockwise_scaled_group_mm_sm100(
    torch::stable::Tensor& output, const torch::stable::Tensor& a,
    const torch::stable::Tensor& b, const torch::stable::Tensor& a_blockscale,
    const torch::stable::Tensor& b_blockscales,
    const torch::stable::Tensor& problem_sizes,
    const torch::stable::Tensor& expert_offsets,
    const torch::stable::Tensor& sf_offsets, int M, int N, int K) {
  using ProblemShape =
      cutlass::gemm::GroupProblemShape<Shape<int32_t, int32_t, int32_t>>;
  using ElementType = cutlass::float_e2m1_t;
  using ElementSFType = cutlass::float_ue8m0_t;
  using ElementA = cutlass::mx_float4_t<cutlass::float_e2m1_t>;
  using ElementB = cutlass::mx_float4_t<cutlass::float_e2m1_t>;

  using ElementC = OutType;
  using ElementD = ElementC;
  using ElementAccumulator = float;
  // Layout definitions
  using LayoutA = cutlass::layout::RowMajor;
  using LayoutB = cutlass::layout::ColumnMajor;
  using LayoutC = cutlass::layout::RowMajor;
  using LayoutD = LayoutC;

  static constexpr int AlignmentA = 32;
  static constexpr int AlignmentB = 32;
  static constexpr int AlignmentC = 128 / cutlass::sizeof_bits<ElementC>::value;
  static constexpr int AlignmentD = 128 / cutlass::sizeof_bits<ElementD>::value;

  // Architecture definitions
  using ArchTag = cutlass::arch::Sm100;
  using EpilogueOperatorClass = cutlass::arch::OpClassTensorOp;
  using MainloopOperatorClass = cutlass::arch::OpClassBlockScaledTensorOp;
  using StageCountType = cutlass::gemm::collective::StageCountAuto;

  using ClusterShape = Shape<_1, _1, _1>;
  struct MMA1SMConfig {
    using MmaTileShape = Shape<_128, _128, _128>;
    using KernelSchedule =
        cutlass::gemm::KernelPtrArrayTmaWarpSpecialized1SmMxf4Sm100;
    using EpilogueSchedule = cutlass::epilogue::PtrArrayTmaWarpSpecialized1Sm;
  };

  using CollectiveEpilogue =
      typename cutlass::epilogue::collective::CollectiveBuilder<
          ArchTag, EpilogueOperatorClass, typename MMA1SMConfig::MmaTileShape,
          ClusterShape, Shape<_128, _64>, ElementAccumulator,
          ElementAccumulator, ElementC, LayoutC*, AlignmentC, ElementD,
          LayoutC*, AlignmentD,
          typename MMA1SMConfig::EpilogueSchedule>::CollectiveOp;

  using CollectiveMainloop =
      typename cutlass::gemm::collective::CollectiveBuilder<
          ArchTag, MainloopOperatorClass, ElementA, LayoutA*, AlignmentA,
          ElementB, LayoutB*, AlignmentB, ElementAccumulator,
          typename MMA1SMConfig::MmaTileShape, ClusterShape,
          cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(
              sizeof(typename CollectiveEpilogue::SharedStorage))>,
          typename MMA1SMConfig::KernelSchedule>::CollectiveOp;

  using GemmKernel =
      cutlass::gemm::kernel::GemmUniversal<ProblemShape, CollectiveMainloop,
                                           CollectiveEpilogue>;

  using Gemm = cutlass::gemm::device::GemmUniversalAdapter<GemmKernel>;
  using StrideA = typename Gemm::GemmKernel::InternalStrideA;
  using StrideB = typename Gemm::GemmKernel::InternalStrideB;
  using StrideC = typename Gemm::GemmKernel::InternalStrideC;
  using StrideD = typename Gemm::GemmKernel::InternalStrideD;

  using LayoutSFA =
      typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFA;
  using LayoutSFB =
      typename Gemm::GemmKernel::CollectiveMainloop::InternalLayoutSFB;
  using ScaleConfig =
      typename Gemm::GemmKernel::CollectiveMainloop::Sm1xxBlkScaledConfig;

  using UnderlyingProblemShape = ProblemShape::UnderlyingProblemShape;
  int num_experts = static_cast<int>(expert_offsets.size(0));

  torch::stable::Tensor a_ptrs =
      torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
                           std::nullopt, a.device());
  torch::stable::Tensor b_ptrs =
      torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
                           std::nullopt, a.device());
  torch::stable::Tensor out_ptrs =
      torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
                           std::nullopt, a.device());
  torch::stable::Tensor a_scales_ptrs =
      torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
                           std::nullopt, a.device());
  torch::stable::Tensor b_scales_ptrs =
      torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
                           std::nullopt, a.device());
  torch::stable::Tensor layout_sfa = torch::stable::empty(
      {num_experts, 5}, torch::headeronly::ScalarType::Long, std::nullopt,
      a.device());
  torch::stable::Tensor layout_sfb = torch::stable::empty(
      {num_experts, 5}, torch::headeronly::ScalarType::Long, std::nullopt,
      a.device());
  torch::stable::Tensor a_strides1 =
      torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
                           std::nullopt, a.device());
  torch::stable::Tensor b_strides1 =
      torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
                           std::nullopt, a.device());
  torch::stable::Tensor c_strides1 =
      torch::stable::empty(num_experts, torch::headeronly::ScalarType::Long,
                           std::nullopt, a.device());

  mxfp4_run_get_group_gemm_starts<LayoutSFA, LayoutSFB, ScaleConfig>(
      a_ptrs, b_ptrs, out_ptrs, a_scales_ptrs, b_scales_ptrs, layout_sfa,
      layout_sfb, a_strides1, b_strides1, c_strides1, a.stride(0) * 2,
      b.stride(1) * 2, output.stride(0), a, b, output, a_blockscale,
      b_blockscales, expert_offsets, sf_offsets, problem_sizes, M, N, K);

  // Create an instance of the GEMM
  Gemm gemm_op;

  UnderlyingProblemShape* problem_sizes_as_shapes =
      static_cast<UnderlyingProblemShape*>(problem_sizes.data_ptr());

  // Set the Scheduler info
  cutlass::KernelHardwareInfo hw_info;
  using RasterOrderOptions = typename cutlass::gemm::kernel::detail::
      PersistentTileSchedulerSm100GroupParams<
          typename ProblemShape::UnderlyingProblemShape>::RasterOrderOptions;
  typename Gemm::GemmKernel::TileSchedulerArguments scheduler;
  scheduler.raster_order = RasterOrderOptions::AlongM;
  hw_info.device_id = a.get_device_index();
  static std::unordered_map<int, int> cached_sm_counts;
  if (cached_sm_counts.find(hw_info.device_id) == cached_sm_counts.end()) {
    cached_sm_counts[hw_info.device_id] =
        cutlass::KernelHardwareInfo::query_device_multiprocessor_count(
            hw_info.device_id);
  }
  hw_info.sm_count = min(cached_sm_counts[hw_info.device_id], INT_MAX);

  // Mainloop Arguments
  typename GemmKernel::MainloopArguments mainloop_args{
      static_cast<const ElementType**>(a_ptrs.data_ptr()),
      static_cast<StrideA*>(a_strides1.data_ptr()),
      static_cast<const ElementType**>(b_ptrs.data_ptr()),
      static_cast<StrideB*>(b_strides1.data_ptr()),
      static_cast<const ElementSFType**>(a_scales_ptrs.data_ptr()),
      reinterpret_cast<LayoutSFA*>(layout_sfa.data_ptr()),
      static_cast<const ElementSFType**>(b_scales_ptrs.data_ptr()),
      reinterpret_cast<LayoutSFB*>(layout_sfb.data_ptr())};

  // Epilogue Arguments
  typename GemmKernel::EpilogueArguments epilogue_args{
      {},  // epilogue.thread
      nullptr,
      static_cast<StrideC*>(c_strides1.data_ptr()),
      static_cast<ElementD**>(out_ptrs.data_ptr()),
      static_cast<StrideC*>(c_strides1.data_ptr())};
  auto& fusion_args = epilogue_args.thread;
  // Scalar epilogue (CUTLASS grouped GEMM): D = 1 * accum + 0 * C
  fusion_args.alpha_ptr = nullptr;
  fusion_args.beta_ptr = nullptr;
  fusion_args.alpha = 1.0f;
  fusion_args.alpha_ptr_array = nullptr;
  fusion_args.dAlpha = {_0{}, _0{}, 0};
  fusion_args.beta = 0.0f;
  fusion_args.beta_ptr_array = nullptr;
  fusion_args.dBeta = {_0{}, _0{}, 0};

  // Gemm Arguments
  typename GemmKernel::Arguments args{
      cutlass::gemm::GemmUniversalMode::kGrouped,
      {num_experts, problem_sizes_as_shapes, nullptr},
      mainloop_args,
      epilogue_args,
      hw_info,
      scheduler};

  size_t workspace_size = Gemm::get_workspace_size(args);
  auto workspace =
      torch::stable::empty(workspace_size, torch::headeronly::ScalarType::Byte,
                           std::nullopt, a.device());
  const cudaStream_t stream = get_current_cuda_stream(a.get_device_index());

  auto can_implement_status = gemm_op.can_implement(args);
  STD_TORCH_CHECK(
      can_implement_status == cutlass::Status::kSuccess,
      "Failed to implement MXFP4 GEMM: status=", (int)can_implement_status);

  // Run the GEMM
  auto status = gemm_op.initialize(args, workspace.data_ptr());
  STD_TORCH_CHECK(status == cutlass::Status::kSuccess,
                  "Failed to initialize MXFP4 GEMM: status=", (int)status,
                  " workspace_size=", workspace_size,
                  " num_experts=", num_experts, " M=", M, " N=", N, " K=", K);

  status = gemm_op.run(args, workspace.data_ptr(), stream);
  STD_TORCH_CHECK(status == cutlass::Status::kSuccess,
                  "Failed to run MXFP4 GEMM");
}

template <typename OutType>
void run_mxfp4_blockwise_scaled_group_mm(
    torch::stable::Tensor& output, const torch::stable::Tensor& a,
    const torch::stable::Tensor& b, const torch::stable::Tensor& a_blockscale,
    const torch::stable::Tensor& b_blockscales,
    const torch::stable::Tensor& problem_sizes,
    const torch::stable::Tensor& expert_offsets,
    const torch::stable::Tensor& sf_offsets, int M, int N, int K) {
  int32_t version_num = get_sm_version_num();
#if defined ENABLE_NVFP4_SM100 && ENABLE_NVFP4_SM100
  if (version_num >= 100 && version_num < 120) {
    run_mxfp4_blockwise_scaled_group_mm_sm100<OutType>(
        output, a, b, a_blockscale, b_blockscales, problem_sizes,
        expert_offsets, sf_offsets, M, N, K);
    return;
  }
#endif
  STD_TORCH_CHECK_NOT_IMPLEMENTED(
      false,
      "No compiled cutlass_mxfp4_group_mm kernel for CUDA device capability: ",
      version_num, ". Required capability: 100");
}

#if defined ENABLE_NVFP4_SM100 && ENABLE_NVFP4_SM100
constexpr auto MXFP4_FLOAT4_E2M1X2 = torch::headeronly::ScalarType::Byte;
// E8M0 scale factors stored as uint8
constexpr auto MXFP4_SF_DTYPE = torch::headeronly::ScalarType::Byte;
#endif

#define CHECK_TYPE(x, st, m)             \
  STD_TORCH_CHECK(x.scalar_type() == st, \
                  ": Inconsistency of torch::stable::Tensor type:", m)
#define CHECK_TH_CUDA(x, m) \
  STD_TORCH_CHECK(x.is_cuda(), m, ": must be a CUDA tensor.")
#define CHECK_CONTIGUOUS(x, m) \
  STD_TORCH_CHECK(x.is_contiguous(), m, ": must be contiguous.")
#define CHECK_INPUT(x, st, m) \
  CHECK_TH_CUDA(x, m);        \
  CHECK_CONTIGUOUS(x, m);     \
  CHECK_TYPE(x, st, m)

void cutlass_mxfp4_group_mm(torch::stable::Tensor& output,
                            const torch::stable::Tensor& a,
                            const torch::stable::Tensor& b,
                            const torch::stable::Tensor& a_blockscale,
                            const torch::stable::Tensor& b_blockscales,
                            const torch::stable::Tensor& problem_sizes,
                            const torch::stable::Tensor& expert_offsets,
                            const torch::stable::Tensor& sf_offsets) {
#if defined ENABLE_NVFP4_SM100 && ENABLE_NVFP4_SM100
  // Input validation
  CHECK_INPUT(a, MXFP4_FLOAT4_E2M1X2, "a");
  CHECK_INPUT(b, MXFP4_FLOAT4_E2M1X2, "b");
  // MXFP4 uses E8M0 scale factors (stored as uint8)
  CHECK_INPUT(a_blockscale, MXFP4_SF_DTYPE, "a_blockscale");
  CHECK_INPUT(b_blockscales, MXFP4_SF_DTYPE, "b_blockscales");

  STD_TORCH_CHECK(
      a_blockscale.dim() == 2,
      "expected a_blockscale to be of shape [num_experts, rounded_m,"
      " k // group_size], observed rank: ",
      a_blockscale.dim())
  STD_TORCH_CHECK(b_blockscales.dim() == 3,
                  "expected b_blockscale to be of shape: "
                  " [num_experts, n, k // group_size], observed rank: ",
                  b_blockscales.dim())
  STD_TORCH_CHECK(problem_sizes.dim() == 2,
                  "problem_sizes must be a 2D tensor");
  STD_TORCH_CHECK(problem_sizes.size(1) == 3,
                  "problem_sizes must have the shape (num_experts, 3)");
  STD_TORCH_CHECK(
      problem_sizes.size(0) == expert_offsets.size(0),
      "Number of experts in problem_sizes must match expert_offsets");
  STD_TORCH_CHECK(
      problem_sizes.scalar_type() == torch::headeronly::ScalarType::Int,
      "problem_sizes must be int32.");

  int M = static_cast<int>(a.size(0));
  int N = static_cast<int>(b.size(1));
  int E = static_cast<int>(b.size(0));
  int K = static_cast<int>(2 * b.size(2));

  if (output.scalar_type() == torch::headeronly::ScalarType::BFloat16) {
    run_mxfp4_blockwise_scaled_group_mm<cutlass::bfloat16_t>(
        output, a, b, a_blockscale, b_blockscales, problem_sizes,
        expert_offsets, sf_offsets, M, N, K);
  } else {
    run_mxfp4_blockwise_scaled_group_mm<cutlass::half_t>(
        output, a, b, a_blockscale, b_blockscales, problem_sizes,
        expert_offsets, sf_offsets, M, N, K);
  }
#else
  STD_TORCH_CHECK_NOT_IMPLEMENTED(
      false,
      "No compiled cutlass_mxfp4_group_mm kernel; build vLLM with "
      "SM100 block-scaled FP4 MoE (ENABLE_NVFP4_SM100) and CUDA 12.8+.");
#endif
}

STABLE_TORCH_LIBRARY_IMPL(_C, CUDA, m) {
  m.impl("cutlass_mxfp4_group_mm", TORCH_BOX(&cutlass_mxfp4_group_mm));
}
