-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add W8A8 (FP8xFP8) Mega MoE support #371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 { | ||
|
|
||
|
|
@@ -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); | ||
| 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); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 🤖 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); | ||
|
|
@@ -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"); | ||
|
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); | ||
|
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, | ||
|
|
@@ -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 | ||
| } | ||
|
|
||
| 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 | ||
|
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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -87,6 +87,7 @@ | |
| transform_weights_for_mega_moe, | ||
| fp8_fp4_mega_moe, | ||
| bf16_mega_moe, | ||
| fp8_fp8_mega_moe, | ||
| ) | ||
|
|
||
| # Some utils | ||
|
|
||
There was a problem hiding this comment.
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_layouttolayout::Data(<bytes>, false)bypasses thenum_bytes % 16 == 0assertion inData::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 atdeep_gemm/include/deep_gemm/layout/mega_moe.cuh:208and that this is the only source of the 512 constraint for the SF layouts (no remaining% 512checks). No behavioral change for 512-aligned shapes. The test's unaligned config (768/1152) still satisfies thehidden % 128 == 0/intermediate % 128 == 0API contract.🤖 v4
There was a problem hiding this comment.
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.