Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
106 changes: 104 additions & 2 deletions csrc/apis/mega.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "../jit/device_runtime.hpp"
#include "../jit_kernels/impls/sm100_bf16_mega_moe.hpp"
#include "../jit_kernels/impls/sm100_fp8_fp4_mega_moe.hpp"
#include "../jit_kernels/impls/sm100_fp8_fp8_mega_moe.hpp"

namespace deep_gemm::mega {

Expand Down Expand Up @@ -57,8 +58,8 @@ get_symm_buffer_size_for_mega_moe(
const auto input_token_layout = layout::Data(hidden * num_mma_elem_bytes);
const auto bf16_token_layout = layout::Data(hidden * 2);
const auto intermediate_token_layout = layout::Data(intermediate_hidden * num_mma_elem_bytes);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion: The 512-alignment relaxation is sound: switching input_sf_layout/intermediate_sf_layout to layout::Data(<bytes>, false) bypasses the num_bytes % 16 == 0 assertion in Data::Data. This is well-justified — these are MN-major SF buffers where TMA strides along the M (token) dimension, so the alignment constraint applies to the block/M-dimension stride, not the per-token K-dim byte count. I verified the assertion logic at deep_gemm/include/deep_gemm/layout/mega_moe.cuh:208 and that this is the only source of the 512 constraint for the SF layouts (no remaining % 512 checks). No behavioral change for 512-aligned shapes. The test's unaligned config (768/1152) still satisfies the hidden % 128 == 0 / intermediate % 128 == 0 API contract.

🤖 v4

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! One addition: in practice hidden is constrained to multiples of 256 — the L2 GEMM has N = hidden and kNumL2BlockNs % 2 == 0 (scheduler/mega_moe.cuh:41, 2-CTA cluster) requires hidden % (2 * BLOCK_N) == 0.

const auto input_sf_layout = layout::Data(with_sf ? hidden / 32 : 0);
const auto intermediate_sf_layout = layout::Data(with_sf ? intermediate_hidden / 32 : 0);
const auto input_sf_layout = layout::Data(with_sf ? hidden / 32 : 0, false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion: The alignment relaxation (require_tma_alignment=false) is safe. These Data objects feed only into buffer-size accounting (layout::Buffer), not TMA box dims. The one place SF is accessed with pointer arithmetic derived from num_bytes is the dispatch uint32_t read (sm100_fp8_fp8_mega_moe.cuh:569 remote_sf_ptr[j], offset src_token_idx * num_bytes), which requires num_bytes % 4 == 0. That is guaranteed by the pre-existing assert hidden % 128 == 0 and intermediate_hidden % 128 == 0 (line 116), since num_bytes = hidden/32 = (hidden/128)*4. Good.

🤖 v3

const auto intermediate_sf_layout = layout::Data(with_sf ? intermediate_hidden / 32 : 0, false);
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 Down Expand Up @@ -253,6 +254,106 @@ static void fp8_fp4_mega_moe(
sym_buffer.zero_();
}

static void fp8_fp8_mega_moe(
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 bool& fast_math,
const int& num_ring_tokens
) {
const auto [l1_weights, l1_weights_sf] = l1_weights_tuple;
const auto [l2_weights, l2_weights_sf] = l2_weights_tuple;

// Config checks
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");
Comment thread
xuzhiyuan1 marked this conversation as resolved.

// Activation checks
const auto activation_clamp =
activation_clamp_opt.value_or(std::numeric_limits<float>::infinity());
DG_HOST_ASSERT(activation_clamp >= 0);

// Tensor checks
DG_HOST_ASSERT(get_major_type_ab(l1_weights) == cute::UMMA::Major::K);
DG_HOST_ASSERT(get_major_type_ab(l2_weights) == cute::UMMA::Major::K);
const auto arch_major = device_runtime->get_arch_major();
const auto [num_experts_per_rank, intermediate_hidden_2, hidden] =
check_grouped_ab_fp8_fp4(l1_weights, cute::UMMA::Major::K, arch_major);
const auto [num_experts_per_rank_, hidden_, intermediate_hidden] =
check_grouped_ab_fp8_fp4(l2_weights, cute::UMMA::Major::K, arch_major);
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());
// FP8 weights must be explicitly checked, as FP4 weights also pass the group checks above
DG_HOST_ASSERT(l1_weights.scalar_type() == torch::kFloat8_e4m3fn);
Comment thread
xuzhiyuan1 marked this conversation as resolved.
DG_HOST_ASSERT(l2_weights.scalar_type() == torch::kFloat8_e4m3fn);

// Check weight SF layout for UE8M0 packing, MN-major, and TMA alignment
constexpr int kGranMN = 1, kGranK = 32;
check_sf_layout(l1_weights_sf, intermediate_hidden * 2, hidden, kGranMN, kGranK,
num_experts_per_rank, true, false, torch::kInt);
check_sf_layout(l2_weights_sf, hidden, intermediate_hidden, kGranMN, kGranK,
num_experts_per_rank, true, false, torch::kInt);

// Check stats counter
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());
}

// Check buffer bytes
// NOTES: FP8xFP8 shares the same symmetric buffer layout as FP8xFP4 (FP8 activations with UE8M0 SF)
const auto num_ranks = static_cast<int>(sym_buffer_ptrs.size());
const auto num_experts_ = num_experts_per_rank * num_ranks;
// NOTES: W8A8 shares the FP8-activation buffer layout with the FP8xFP4 path
const auto [num_required_bytes, slice] = get_symm_buffer_size_for_mega_moe(
num_ranks, num_experts,
num_max_tokens_per_rank, num_topk,
hidden, intermediate_hidden,
"fp8xfp4", activation, num_ring_tokens);
DG_HOST_ASSERT(sym_buffer.nbytes() >= static_cast<size_t>(num_required_bytes));
DG_HOST_ASSERT(num_experts == num_experts_);

// Already registered tensors
const auto [x, x_sf, topk_idx, topk_weights, l1_acts, l1_acts_sf, l2_acts, l2_acts_sf] = slice(sym_buffer);

// Dispatch into different architectures
if (arch_major == 10) {
sm100_fp8_fp8_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_clamp, fast_math);
} else {
DG_HOST_UNREACHABLE("Unsupported architecture");
}

// Zero the entire symmetric buffer for debug mode
// NOTES: caller must re-copy inputs into the buffer before each kernel call
if (get_env<int>("DG_COMM_KERNEL_DEBUG"))
sym_buffer.zero_();
}

static void bf16_mega_moe(
const torch::Tensor& y,
const torch::Tensor& l1_weights,
Expand Down Expand Up @@ -339,6 +440,7 @@ static void register_apis(pybind11::module_& m) {
m.def("get_ring_limit_for_mega_moe", &get_ring_limit_for_mega_moe);
m.def("get_symm_buffer_size_for_mega_moe", &get_symm_buffer_size_for_mega_moe);
m.def("fp8_fp4_mega_moe", &fp8_fp4_mega_moe);
m.def("fp8_fp8_mega_moe", &fp8_fp8_mega_moe);
m.def("bf16_mega_moe", &bf16_mega_moe);
#endif
}
Expand Down
234 changes: 234 additions & 0 deletions csrc/jit_kernels/impls/sm100_fp8_fp8_mega_moe.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
#pragma once

// NOTES: derived from `sm100_fp8_fp4_mega_moe.hpp` with FP8 (e4m3) weights instead of FP4.
// The weight SF remains 4-packed UE8M0 stored in `torch::kInt`, so the SF layout checks,
// TMA descriptors and launch logic are identical to the FP4 version

#include <torch/python.h>

#include "../../jit/compiler.hpp"
#include "../../jit/kernel_runtime.hpp"
#include "../../utils/exception.hpp"
#include "../../utils/format.hpp"
#include "runtime_utils.hpp"

#include <deep_gemm/layout/mega_moe.cuh>
#include <deep_gemm/layout/sym_buffer.cuh>

#include "../heuristics/mega_moe.hpp"

namespace deep_gemm {

class SM100FP8FP8MegaMoERuntime final : public LaunchRuntime<SM100FP8FP8MegaMoERuntime> {
public:
struct Args {
// Templated arguments
int num_max_tokens_per_rank;
int hidden, intermediate_hidden;
int num_experts, num_topk;
int num_ranks;
float activation_clamp;
bool fast_math;
MegaMoEConfig config;

// Runtime arguments
void* y;
int* cumulative_local_expert_recv_stats;
int num_tokens;
layout::SymBuffer<> sym_buffer_ptrs;

// Tensormap
CUtensorMap tensor_map_l1_acts;
CUtensorMap tensor_map_l1_acts_sf;
CUtensorMap tensor_map_l1_weights;
CUtensorMap tensor_map_l1_weights_sf;
CUtensorMap tensor_map_l1_output;
CUtensorMap tensor_map_l2_acts;
CUtensorMap tensor_map_l2_acts_sf;
CUtensorMap tensor_map_l2_weights;
CUtensorMap tensor_map_l2_weights_sf;

// Launch configs
Comment thread
xuzhiyuan1 marked this conversation as resolved.
LaunchArgs launch_args;
};

static std::string generate_impl(const Args& args) {
return fmt::format(R"(
#include <deep_gemm/impls/sm100_fp8_fp8_mega_moe.cuh>

using namespace deep_gemm;

static void __instantiate_kernel() {{
auto ptr = reinterpret_cast<void*>(&sm100_fp8_fp8_mega_moe_impl<
{},
{}, {},
{}, {},
{},
{}, {}, {},
{},
{}, {},
{},
{},
{},
{},
{}, {}, {},
{}, {},
{},
{}
>);
}};
)", args.num_max_tokens_per_rank,
args.hidden, args.intermediate_hidden,
args.num_experts, args.num_topk,
args.config.num_experts_per_wave,
args.config.block_m, args.config.block_n, args.config.block_k,
args.config.store_block_m,
args.config.sf_block_m, args.config.sf_block_n,
args.config.num_ring_tokens,
args.config.num_sf_ring_tokens,
args.config.num_stages,
args.config.num_bytes_per_pull,
args.config.num_dispatch_threads, args.config.num_non_epilogue_threads, args.config.num_epilogue_threads,
args.launch_args.grid_dim.first, args.num_ranks,
to_string(args.activation_clamp),
args.fast_math ? "true" : "false");
}

static void launch_impl(const KernelHandle& kernel, const LaunchConfigHandle& config, Args args) {
// TODO: optimize `args` copy
DG_CUDA_UNIFIED_CHECK(launch_kernel(kernel, config,
args.y,
args.cumulative_local_expert_recv_stats,
args.num_tokens,
args.sym_buffer_ptrs,
args.tensor_map_l1_acts,
args.tensor_map_l1_acts_sf,
args.tensor_map_l1_weights,
args.tensor_map_l1_weights_sf,
args.tensor_map_l1_output,
args.tensor_map_l2_acts,
args.tensor_map_l2_acts_sf,
args.tensor_map_l2_weights,
args.tensor_map_l2_weights_sf
));
}
};

static void sm100_fp8_fp8_mega_moe(
const torch::Tensor& y,
const torch::Tensor& l1_acts, const torch::Tensor& l1_acts_sf,
const torch::Tensor& l2_acts, const torch::Tensor& l2_acts_sf,
const torch::Tensor& l1_weights, const torch::Tensor& l2_weights,
const torch::Tensor& l1_weights_sf, const torch::Tensor& l2_weights_sf,
const std::optional<torch::Tensor> cumulative_local_expert_recv_stats,
const std::vector<int64_t>& sym_buffer_ptrs,
const int& rank_idx, const int& num_max_tokens_per_rank,
const int& num_experts_per_rank,
const int& num_tokens, const int& num_topk,
const int& hidden, const int& intermediate_hidden,
const float& activation_clamp,
const bool& fast_math
) {
const auto num_ranks = static_cast<int>(sym_buffer_ptrs.size());
const auto num_experts = num_experts_per_rank * num_ranks;
const auto num_ring_tokens = static_cast<int>(l1_acts.size(0));
const auto num_sf_ring_tokens = static_cast<int>(l1_acts_sf.size(0));

// Heuristics
const auto config = get_mega_moe_config(
num_ranks, num_experts, num_experts_per_rank,
num_max_tokens_per_rank, num_tokens, num_topk, hidden, intermediate_hidden,
num_ring_tokens, num_sf_ring_tokens,
MmaKind::MXFP8FP4);

// Make tensormap
constexpr int kGranK = 32;
const int sf_smem_outer_dim = config.block_k / (kGranK * 4);
const auto tensor_map_l1_acts = make_tma_2d_desc(l1_acts,
hidden, config.num_ring_tokens,
config.block_k, config.load_block_m,
static_cast<int>(l1_acts.stride(-2)),
config.swizzle_acts_mode);
const auto tensor_map_l1_acts_sf = make_tma_sf_desc(cute::UMMA::Major::MN, l1_acts_sf,
config.num_sf_ring_tokens, hidden,
config.sf_block_m, kGranK,
1, 0, 0, false,
sf_smem_outer_dim);
const auto tensor_map_l1_weights = make_tma_2d_desc(l1_weights,
hidden, num_experts_per_rank * intermediate_hidden * 2,
config.block_k, config.load_block_n,
static_cast<int>(l1_weights.stride(-2)),
config.swizzle_weights_mode);
const auto tensor_map_l1_weights_sf = make_tma_sf_desc(cute::UMMA::Major::MN, l1_weights_sf,
intermediate_hidden * 2, hidden,
config.block_n, kGranK,
num_experts_per_rank, 0, 0, false,
sf_smem_outer_dim);
// NOTES: L1 output and L2 activations are essentially the same tensor.
// Post-SwiGLU output has half the N width (`BLOCK_N / 2` per input tile),
// so the swizzle mode is also halved (128 -> 64).
const auto tensor_map_l1_output = make_tma_2d_desc(l2_acts,
intermediate_hidden, config.num_ring_tokens,
config.block_n / 2, config.store_block_m,
static_cast<int>(l2_acts.stride(-2)),
config.swizzle_acts_mode / 2);
const auto tensor_map_l2_acts = make_tma_2d_desc(l2_acts,
intermediate_hidden, config.num_ring_tokens,
config.block_k, config.load_block_m,
static_cast<int>(l2_acts.stride(-2)),
config.swizzle_acts_mode);
const auto tensor_map_l2_acts_sf = make_tma_sf_desc(cute::UMMA::Major::MN, l2_acts_sf,
config.num_sf_ring_tokens, intermediate_hidden,
config.sf_block_m, kGranK,
1, 0, 0, false,
sf_smem_outer_dim);
const auto tensor_map_l2_weights = make_tma_2d_desc(l2_weights,
intermediate_hidden, num_experts_per_rank * hidden,
config.block_k, config.load_block_n,
static_cast<int>(l2_weights.stride(-2)),
config.swizzle_weights_mode);
const auto tensor_map_l2_weights_sf = make_tma_sf_desc(cute::UMMA::Major::MN, l2_weights_sf,
hidden, intermediate_hidden,
config.block_n, kGranK,
num_experts_per_rank, 0, 0, false,
sf_smem_outer_dim);

// Stats can be optional
int* cumulative_local_expert_recv_stats_ptr = nullptr;
if (cumulative_local_expert_recv_stats.has_value())
cumulative_local_expert_recv_stats_ptr = cumulative_local_expert_recv_stats->data_ptr<int>();

// Launch
const auto num_sms = device_runtime->get_num_sms();
const SM100FP8FP8MegaMoERuntime::Args args = {
.num_max_tokens_per_rank = num_max_tokens_per_rank,
.hidden = hidden, .intermediate_hidden = intermediate_hidden,
.num_experts = num_experts, .num_topk = num_topk,
.num_ranks = num_ranks,
.activation_clamp = activation_clamp,
.fast_math = fast_math,
.config = config,
.y = y.data_ptr(),
.cumulative_local_expert_recv_stats = cumulative_local_expert_recv_stats_ptr,
.num_tokens = num_tokens,
.sym_buffer_ptrs = layout::SymBuffer<>(sym_buffer_ptrs, rank_idx),
.tensor_map_l1_acts = tensor_map_l1_acts,
.tensor_map_l1_acts_sf = tensor_map_l1_acts_sf,
.tensor_map_l1_weights = tensor_map_l1_weights,
.tensor_map_l1_weights_sf = tensor_map_l1_weights_sf,
.tensor_map_l1_output = tensor_map_l1_output,
.tensor_map_l2_acts = tensor_map_l2_acts,
.tensor_map_l2_acts_sf = tensor_map_l2_acts_sf,
.tensor_map_l2_weights = tensor_map_l2_weights,
.tensor_map_l2_weights_sf = tensor_map_l2_weights_sf,
.launch_args = LaunchArgs(num_sms,
config.num_dispatch_threads + config.num_non_epilogue_threads + config.num_epilogue_threads,
config.smem_size, 2)
};

const auto code = SM100FP8FP8MegaMoERuntime::generate(args);
const auto runtime = compiler->build("sm100_fp8_fp8_mega_moe", code);
SM100FP8FP8MegaMoERuntime::launch(runtime, args);
}

} // namespace deep_gemm
1 change: 1 addition & 0 deletions deep_gemm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
transform_weights_for_mega_moe,
fp8_fp4_mega_moe,
bf16_mega_moe,
fp8_fp8_mega_moe,
)

# Some utils
Expand Down
4 changes: 2 additions & 2 deletions deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ sm100_fp8_fp4_mega_moe_impl(void* y,
constexpr auto fp8_token_layout = layout::Data(kHidden);
constexpr auto bf16_token_layout = layout::Data(kHidden * sizeof(nv_bfloat16));
constexpr auto fp8_intermediate_token_layout = layout::Data(kIntermediateHidden);
constexpr auto fp8_sf_layout = layout::Data(kHidden / 32);
constexpr auto fp8_intermediate_sf_layout = layout::Data(kIntermediateHidden / 32);
constexpr auto fp8_sf_layout = layout::Data(kHidden / 32, false);
constexpr auto fp8_intermediate_sf_layout = layout::Data(kIntermediateHidden / 32, false);
constexpr auto input_topk_idx_layout = layout::Data(kNumTopk * sizeof(int64_t), false);
constexpr auto input_topk_weights_layout = layout::Data(kNumTopk * sizeof(float), false);
constexpr auto l1_topk_weights_layout = layout::Data(sizeof(float), false);
Expand Down
Loading