Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2deff45
Add SM90 FP4 MegaMoE implementation
Jun 22, 2026
546e622
Avoid L1 N32 swapAB bucket for FP4 MegaMoE
Jun 24, 2026
d45e0c9
FP4 swapAB L1: align routing-weight fold order with non-swap path
Jun 28, 2026
2b9a349
Fix SM90 FP4 MegaMoE build against post-#364 signatures
Jul 1, 2026
7dcb523
Simplify SM90 FP4 MegaMoE heuristics to FP8 parity
Jul 14, 2026
dd928e4
Raise SM90 FP4 MegaMoE decode->prefill boundary to e=80
Jul 14, 2026
fd96bc2
Use continuous FP32 activation scale in SM90 FP4 MegaMoE
Jul 19, 2026
de19b8b
Tune SM90 MegaMoE decode heuristics for GLM5.2
Jul 5, 2026
52c4c1b
Add SM90 FP4 MegaMoE SiTU support for Kimi K3
Jul 29, 2026
9600fe9
Use fast tanh identities for SM90 FP4 MegaMoE SiTU fast-math
Jul 29, 2026
f200bc5
Load FP4 MegaMoE activation scales with a single 2D TMA
Jul 29, 2026
30b2d3a
Default SiTU to 128/64 activation scales with e=512 prefill boundary
Jul 29, 2026
a7dc830
Enable swapAB for SiTU with the 128/64 recipe
Jul 29, 2026
c5ff775
Add SiTU 128/64 prefill mid band for e in (80, 112]
Jul 30, 2026
cc07120
Calibrate SiTU prefill mid-band lower bound to e=76
Jul 30, 2026
296931f
Raise FP4 swapAB default bound to e=20
Jul 30, 2026
e7d591d
Select SiTU 128/64 config via an analytic cost model
Jul 30, 2026
f19a187
Add experimental FP4 tuning env overrides from small-batch probing
Jul 30, 2026
cbe8ccb
Hoist tile-invariant dispatch out of the math K-stage loop
Jul 30, 2026
f13723a
Add experts-per-wave probe env from 512+ tuning sweep
Jul 30, 2026
f20c06c
Add normal-path stage breakdown and wide-load-decode probe env
Jul 30, 2026
6cd3699
Remove dead experiment probe envs
Jul 31, 2026
4057fa3
Clean up SM90 FP4 production path and compact SF pools
Sep 4, 2026
68723eb
Resolve UInt8 alias after rebasing onto dev
Sep 4, 2026
af3764a
Use the SM90 legacy scheduler for FP4 MegaMoE
Sep 4, 2026
95a265e
Keep SM90 SF sizing local to the API
Sep 4, 2026
e630a49
Fix SM90 FP4 API and Kimi test coverage
Sep 4, 2026
ad6408a
Clean up SM90 FP4 production configuration
Sep 4, 2026
01a4862
Clean up SM90 MegaMoE API handling
Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 191 additions & 18 deletions csrc/apis/sm90_mega.hpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
#pragma once

#include <functional>
#include <limits>
#include <optional>
#include <string>
#include <tuple>
#include <vector>

#include "mega.hpp"
#include "../jit/device_runtime.hpp"
#include "../jit_kernels/impls/sm90_fp8_fp4_mega_moe.hpp"
#include "../jit_kernels/impls/sm90_fp8_mega_moe.hpp"
#include "../jit_kernels/impls/sm90_mega_moe_pre_dispatch.hpp"
#include "../utils/layout.hpp"
#include "../utils/system.hpp"

namespace deep_gemm::mega {

static int get_token_alignment_for_sm90_mega_moe() {
return layout::kLCMCandidateBlockM;
}

static void mega_moe_pre_dispatch_sm90(
const torch::Tensor& x,
const torch::Tensor& topk_idx,
Expand All @@ -30,6 +35,68 @@ static void mega_moe_pre_dispatch_sm90(
num_tokens, group_size, routed_scaling_factor);
}

static bool is_packed_fp4_storage_sm90(const torch::Tensor& t) {
return t.scalar_type() == kPackedFP4 or t.scalar_type() == torch::kByte;
}

static std::tuple<int, int, int> check_grouped_ab_sm90_fp4_mega_moe(const torch::Tensor& ab) {
const auto [num_groups, mn, packed_k] = get_shape<3>(ab);
DG_HOST_ASSERT(is_packed_fp4_storage_sm90(ab));
DG_HOST_ASSERT(get_major_type_ab(ab) == cute::UMMA::Major::K);
DG_HOST_ASSERT(packed_k > 0 and packed_k % 64 == 0);
return {num_groups, mn, packed_k * 2};
}

static void check_sm90_fp4_sfb_layout(const torch::Tensor& sf,
const int& mn, const int& k,
const int& num_groups) {
DG_HOST_ASSERT(sf.scalar_type() == torch::kInt);
DG_HOST_ASSERT(sf.dim() == 3);
DG_HOST_ASSERT(sf.size(0) == num_groups);
DG_HOST_ASSERT(sf.size(1) == mn);
DG_HOST_ASSERT(sf.size(2) == ceil_div(k, 128));
DG_HOST_ASSERT(sf.is_contiguous());
}

struct FP4SM90APIDefaults {
bool wide_load_decode;
bool early_b_decode;
bool decode_done_mbarrier;
bool ss_nsplit;
bool swap_ab;
};

static FP4SM90APIDefaults get_fp4_sm90_api_defaults(
const int& num_experts_per_rank, const int& num_tokens, const int& num_topk,
const bool& use_situ) {
// Select the decode, prefill, and swapAB features from the same routing
// density so the generated kernel uses a coherent configuration bundle.
const float expected_tokens_per_expert =
static_cast<float>(num_tokens) * num_topk / num_experts_per_rank;
// SiTU uses the analytic cost model while SwiGLU keeps its scalar boundary;
// share the predicate with the block-config heuristics so the feature
// bundle never mixes.
const bool prefill_band = is_fp4_sm90_prefill_band(
expected_tokens_per_expert, use_situ);
const bool decode_band =
expected_tokens_per_expert > 0.0f and !prefill_band;
// SwiGLU uses a fixed low-density swapAB boundary. SiTU derives swapAB
// from the same cost comparison as its decode/prefill decision.
const bool swap_ab =
use_situ
? (get_fp4_sm90_situ_config_kind(expected_tokens_per_expert) ==
FP4SM90ConfigKind::kSwapAB)
: (decode_band and
expected_tokens_per_expert < kSM90FP4SwiGLUSwapABMaxE);
return {
/*wide_load_decode=*/ decode_band,
/*early_b_decode=*/ prefill_band,
/*decode_done_mbarrier=*/ expected_tokens_per_expert > 0.0f,
/*ss_nsplit=*/ prefill_band,
/*swap_ab=*/ swap_ab
};
}

static std::tuple<int64_t, std::function<std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor>(const torch::Tensor&)>>
get_symm_buffer_size_for_sm90_mega_moe(
const int& num_ranks, const int& num_experts,
Expand All @@ -38,16 +105,17 @@ get_symm_buffer_size_for_sm90_mega_moe(
const bool& use_fp8_dispatch, const std::string& activation) {
DG_HOST_ASSERT(num_experts % num_ranks == 0);
DG_HOST_ASSERT(use_fp8_dispatch);
DG_HOST_ASSERT(activation == "swiglu");

DG_HOST_ASSERT(activation == "swiglu" or activation == "situ");
const auto workspace = layout::SM90Workspace(
nullptr, num_ranks, num_experts, num_max_tokens_per_rank, num_topk);

const auto fp8_token_layout = layout::Data(hidden);
const auto bf16_token_layout = layout::Data(hidden * 2);
const auto fp8_intermediate_token_layout = layout::Data(intermediate_hidden);
const auto fp8_sf_layout = layout::Data(hidden / 32);
const auto fp8_intermediate_sf_layout = layout::Data(intermediate_hidden / 16);
const auto fp8_sf_layout =
layout::Data(hidden * static_cast<int>(sizeof(float)) / kSM90FP4L1ActSFGranK);
const auto fp8_intermediate_sf_layout =
layout::Data(intermediate_hidden * static_cast<int>(sizeof(float)) / kSM90FP4L2ActSFGranK);
const auto input_topk_idx_layout = layout::Data(num_topk * sizeof(int64_t), false);
const auto input_topk_weights_layout = layout::Data(num_topk * sizeof(float), false);
const auto l1_topk_weights_layout = layout::Data(sizeof(float), false);
Expand All @@ -66,13 +134,10 @@ get_symm_buffer_size_for_sm90_mega_moe(
input_topk_idx_buffer.get_end_ptr());

const auto num_max_pool_tokens = static_cast<int>(workspace.num_max_pool_tokens);
int num_max_padded_sf_pool_tokens = 0;
for (int block_m: layout::kCandidateBlockM) {
num_max_padded_sf_pool_tokens = std::max(
num_max_padded_sf_pool_tokens,
layout::get_num_sf_ring_tokens(num_max_pool_tokens, block_m)
);
}
constexpr int kMinSM90MegaMoEBlockM = 64;
const auto num_max_padded_sf_pool_tokens = static_cast<int>(
layout::get_num_sf_ring_tokens(
num_max_pool_tokens, kMinSM90MegaMoEBlockM));

const auto l1_token_buffer = layout::Buffer(
fp8_token_layout, 1, num_max_pool_tokens,
Expand All @@ -96,6 +161,8 @@ get_symm_buffer_size_for_sm90_mega_moe(
l2_sf_buffer.get_end_ptr());

DG_HOST_ASSERT(hidden % 128 == 0 and intermediate_hidden % 128 == 0);
DG_HOST_ASSERT(hidden % kSM90FP4L1ActSFGranK == 0);
DG_HOST_ASSERT(intermediate_hidden % kSM90FP4L2ActSFGranK == 0);

auto slice_input_buffers = [=](const torch::Tensor& buffer) {
auto x = torch::from_blob(
Expand All @@ -104,7 +171,7 @@ get_symm_buffer_size_for_sm90_mega_moe(
torch::TensorOptions().dtype(torch::kFloat8_e4m3fn).device(buffer.device()));
auto x_sf = torch::from_blob(
math::advance_ptr(buffer.data_ptr(), reinterpret_cast<int64_t>(input_sf_buffer.base)),
{num_max_tokens_per_rank, hidden / 128},
{num_max_tokens_per_rank, hidden / kSM90FP4L1ActSFGranK},
torch::TensorOptions().dtype(torch::kFloat32).device(buffer.device()));
auto topk_idx = torch::from_blob(
math::advance_ptr(buffer.data_ptr(), reinterpret_cast<int64_t>(input_topk_idx_buffer.base)),
Expand All @@ -120,7 +187,7 @@ get_symm_buffer_size_for_sm90_mega_moe(
torch::TensorOptions().dtype(torch::kFloat8_e4m3fn).device(buffer.device()));
auto l1_acts_sf = torch::from_blob(
math::advance_ptr(buffer.data_ptr(), reinterpret_cast<int64_t>(l1_sf_buffer.base)),
{num_max_padded_sf_pool_tokens, hidden / 128},
{num_max_padded_sf_pool_tokens, hidden / kSM90FP4L1ActSFGranK},
{1, num_max_padded_sf_pool_tokens},
torch::TensorOptions().dtype(torch::kFloat32).device(buffer.device()));
auto l2_acts = torch::from_blob(
Expand All @@ -129,7 +196,7 @@ get_symm_buffer_size_for_sm90_mega_moe(
torch::TensorOptions().dtype(torch::kFloat8_e4m3fn).device(buffer.device()));
auto l2_acts_sf = torch::from_blob(
math::advance_ptr(buffer.data_ptr(), reinterpret_cast<int64_t>(l2_sf_buffer.base)),
{num_max_padded_sf_pool_tokens, intermediate_hidden / 64},
{num_max_padded_sf_pool_tokens, intermediate_hidden / kSM90FP4L2ActSFGranK},
{1, num_max_padded_sf_pool_tokens},
torch::TensorOptions().dtype(torch::kFloat32).device(buffer.device()));
return std::make_tuple(x, x_sf, topk_idx, topk_weights, l1_acts, l1_acts_sf, l2_acts, l2_acts_sf);
Expand Down Expand Up @@ -221,4 +288,110 @@ static void fp8_mega_moe(
sym_buffer.zero_();
}

static void fp8_fp4_mega_moe_sm90(
const torch::Tensor& y,
const std::tuple<torch::Tensor, torch::Tensor>& l1_weights_tuple,
const std::tuple<torch::Tensor, torch::Tensor>& l2_weights_tuple,
const std::optional<torch::Tensor>& cumulative_local_expert_recv_stats,
const torch::Tensor& sym_buffer,
const std::vector<int64_t>& sym_buffer_ptrs, const int& rank_idx,
const int& num_max_tokens_per_rank,
const int& num_experts, const int& num_topk,
const std::tuple<int, int, int>& recipe,
const std::string& activation,
const std::optional<float>& activation_clamp_opt,
const std::optional<float>& activation_alpha_opt,
const std::optional<float>& activation_linear_beta_opt,
const bool& fast_math
) {
const auto [l1_weights, l1_weights_sf] = l1_weights_tuple;
const auto [l2_weights, l2_weights_sf] = l2_weights_tuple;

const auto arch_major = device_runtime->get_arch_major();
DG_HOST_ASSERT(arch_major == 9);

const auto num_tokens = static_cast<int>(y.size(0));
const auto [rm, rn, rk] = recipe;
DG_HOST_ASSERT(rm == 1 and rn == 1 and rk == 32);
DG_HOST_ASSERT(activation == "swiglu" or activation == "situ");
const bool use_situ = activation == "situ";
DG_HOST_ASSERT(not use_situ or not activation_clamp_opt.has_value());

const auto activation_clamp =
activation_clamp_opt.value_or(std::numeric_limits<float>::infinity());
DG_HOST_ASSERT(activation_clamp >= 0);
const auto activation_alpha = activation_alpha_opt.value_or(4.0f);
const auto activation_linear_beta = activation_linear_beta_opt.value_or(25.0f);
DG_HOST_ASSERT(not use_situ or
(activation_alpha > 0.0f and activation_linear_beta > 0.0f));

const auto [num_experts_per_rank, intermediate_hidden_2, hidden] =
check_grouped_ab_sm90_fp4_mega_moe(l1_weights);
const auto [num_experts_per_rank_, hidden_, intermediate_hidden] =
check_grouped_ab_sm90_fp4_mega_moe(l2_weights);
DG_HOST_ASSERT(num_tokens <= num_max_tokens_per_rank);
DG_HOST_ASSERT(num_experts_per_rank == num_experts_per_rank_);
DG_HOST_ASSERT(hidden == hidden_);
DG_HOST_ASSERT(intermediate_hidden_2 == 2 * intermediate_hidden);
DG_HOST_ASSERT(l1_weights.is_contiguous() and l2_weights.is_contiguous());
DG_HOST_ASSERT(hidden % 128 == 0 and intermediate_hidden % 128 == 0);
DG_HOST_ASSERT(intermediate_hidden / 64 <= 64);

check_sm90_fp4_sfb_layout(l1_weights_sf, intermediate_hidden * 2, hidden,
num_experts_per_rank);
check_sm90_fp4_sfb_layout(l2_weights_sf, hidden, intermediate_hidden,
num_experts_per_rank);

if (cumulative_local_expert_recv_stats.has_value()) {
DG_HOST_ASSERT(cumulative_local_expert_recv_stats->scalar_type() == torch::kInt);
DG_HOST_ASSERT(cumulative_local_expert_recv_stats->numel() == num_experts_per_rank);
DG_HOST_ASSERT(cumulative_local_expert_recv_stats->is_contiguous());
}

const auto num_ranks = static_cast<int>(sym_buffer_ptrs.size());
const auto num_experts_ = num_experts_per_rank * num_ranks;
const auto [num_required_bytes, slice] = get_symm_buffer_size_for_sm90_mega_moe(
num_ranks, num_experts,
num_max_tokens_per_rank, num_topk,
hidden, intermediate_hidden,
true, activation);
DG_HOST_ASSERT(sym_buffer.nbytes() >= static_cast<size_t>(num_required_bytes));
DG_HOST_ASSERT(num_experts == num_experts_);

const auto [x, x_sf, topk_idx, topk_weights, l1_acts, l1_acts_sf, l2_acts, l2_acts_sf] = slice(sym_buffer);
(void)x;
(void)x_sf;
(void)topk_idx;
(void)topk_weights;

DG_HOST_ASSERT(get_env<int>("DG_USE_FP4_ACTS") == 0);
DG_HOST_ASSERT(get_env<int>("DG_USE_FP8_COMBINE") == 0);

const auto fp4_defaults = get_fp4_sm90_api_defaults(
num_experts_per_rank, num_tokens, num_topk, use_situ);
sm90_fp8_fp4_mega_moe(y,
l1_acts, l1_acts_sf,
l2_acts, l2_acts_sf,
l1_weights, l2_weights,
l1_weights_sf, l2_weights_sf,
cumulative_local_expert_recv_stats,
sym_buffer_ptrs,
rank_idx, num_max_tokens_per_rank,
num_experts_per_rank,
num_tokens, num_topk,
hidden, intermediate_hidden,
activation,
activation_alpha,
activation_linear_beta,
activation_clamp, fast_math,
fp4_defaults.wide_load_decode,
fp4_defaults.early_b_decode,
fp4_defaults.decode_done_mbarrier,
fp4_defaults.ss_nsplit,
fp4_defaults.swap_ab);

if (get_env<int>("DG_COMM_KERNEL_DEBUG"))
sym_buffer.zero_();
}

} // namespace deep_gemm::mega
Loading