Skip to content

[SM100][MegaMoE] Fuse and publish Kimi K3's BF16 shared expert - #416

Open
gcanlin wants to merge 5 commits into
deepseek-ai:nv_devfrom
gcanlin:feat/kimi-k3-fp8fp4-bf16-shared
Open

gcanlin wants to merge 5 commits into
deepseek-ai:nv_devfrom
gcanlin:feat/kimi-k3-fp8fp4-bf16-shared

Conversation

@gcanlin

@gcanlin gcanlin commented Aug 24, 2026

Copy link
Copy Markdown

Summary

This PR extends the SM100 FP8/FP4 MegaMoE path with an optional BF16 shared expert for latent-MoE models such as Kimi K3. It schedules routed FP8/FP4 experts and BF16 shared L1/L2 together, applies the routed RMSNorm contract, and can publish each TP-sharded shared partial directly to a downstream symmetric workspace.

Kimi K3's routed output is latent-width (3584), while its shared output is hidden-width (7168). The final 3584 -> 7168 projection and residual combination therefore remain in the dependent vLLM tail rather than in this kernel.

This update adds sequence-parallel token publication. The routed branch still processes SP-local tokens, while the shared branch may process a different, gathered token count. Each shared partial is written directly to the rank that owns the corresponding output token.

Implementation

  • Thread num_shared_tokens independently from routed num_tokens through host validation, tensor-map creation, kernel arguments, and the MegaMoE scheduler. Shared L1/L2 task generation now uses the shared token count; routed dispatch remains unchanged.
  • Add fp8_fp4_mega_moe_bf16_shared_sp_rs as an explicit opt-in wrapper. Existing dense shared output and hidden-dimension ReduceScatter publication APIs retain their behavior.
  • In SP publication mode, shared epilogue stores use:
    • tokens_per_rank = num_shared_tokens / num_ranks;
    • destination_rank = global_token / tokens_per_rank;
    • destination_token = global_token % tokens_per_rank;
    • workspace layout [generation, destination_token, source_rank, shared_hidden].
  • The destination receives the complete hidden vector from every TP source rank. The downstream consumer reduces the source-rank dimension locally and combines it with the routed up-projection.
  • Validate that the global shared token count is rank-divisible, workspace dimensions match the selected publication mode, peer pointers cover every rank, and the generation/byte-stride metadata satisfies the cross-repository ABI.
  • Preserve the existing hidden-sharded publication path, including the generalized vector_n - destination_rank * shard column offset that avoids underflow when a shard boundary crosses a 128-column store tile.

The new behavior is compile-time specialized and optional. Calls that do not request BF16 shared work or publication instantiate the existing kernel behavior.

Integration dependencies

The consumer is vllm-project/vllm#53556. The TP8 x PP2 sequence-parallel benchmark temporarily stacked that PR on vllm-project/vllm#54347; #54347 is not merged into #53556 and is not a DeepGEMM dependency.

Test Plan

Repository regression

tests/test_mega_moe_situ.py validates independent routed/shared token counts, fused RMSNorm, dense BF16 shared output, the existing published output, and the new SP publication API. Published BF16 values are compared bitwise with dense output.

python3 tests/test_mega_moe_situ.py

TP8 remote-publication regression

An 8-GPU B200 harness gives each TP rank different shared weights, gathers every dense partial as the reference, calls fp8_fp4_mega_moe_bf16_shared_sp_rs, and compares every destination workspace value in [local_token, source_rank, hidden] order. The test passed twice with Kimi K3 matrix widths.

End-to-end setup

The dependent vLLM integration used two 8xB200 nodes, TP8 x PP2 x EP8, VLLM_KIMI_K3_SHARD_SP_SHARED_EXPERT=1, VLLM_KIMI_K3_GEMM_RS=1, max_num_seqs=128, and max_num_batched_tokens=32768. Baseline and fused runs used identical nodes, image, model, scheduler settings, exact request lengths, four warmups, and seed=2026.

Test Results

TP8 producer microbenchmark

The benchmark uses Kimi K3 matrix widths (routed H/I=3584/3072, shared H/I per TP rank=7168/768), local routed M=8, global shared M=64, one routed expert per rank for the isolated replay, and 100 measured iterations. Reported latency is the maximum rank time.

Run Dense shared output SP token-owner publication Change
1 42.487 us 42.944 us +1.08%
2 42.368 us 43.002 us +1.50%
Mean 42.428 us 42.973 us +1.29%

The remote token-owner publication adds about 0.55 us to the producer kernel and replaces downstream full shared-output materialization plus SP reduction.

The original non-SP destination-scatter replay remains neutral at its measured shapes:

M Existing shared output Published shared output Change
32 0.2032 ms 0.2030 ms -0.09%
64 0.2664 ms 0.2666 ms +0.04%

Downstream SP-sharded prefill

Workload: 8192 input / 1 output, concurrency 8, 128 requests.

Run SHARD=1 baseline total tok/s Fused + SP publication total tok/s Change
1 53,543.40 54,523.92 +1.83%
2 53,084.73 54,776.67 +3.19%
Mean 53,314.06 54,650.29 +2.51%

Mean median TTFT improves by 3.55% (1230.43 -> 1186.76 ms). These end-to-end results include the dependent vLLM targeted gather and local projection tail; they are not a standalone claim for the DeepGEMM producer.

Downstream decode

For 128 input / 1024 output at concurrency 32, the fused runs measured 773.91, 799.72, and 800.06 output tok/s; the SHARD=1 baseline measured 780.46 and 783.86 tok/s. Including the first cold fused run gives +1.16%; steady-state second-run comparison gives +2.02%.

Accuracy

The original fused non-SP integration was evaluated on the complete GSM8K v3 test split with lm-eval 0.4.12, 5-shot chat-template evaluation, and greedy decoding. All 1,319 samples were retained. The SP-token extension is covered by the TP8 bitwise and serving regressions above; this full-dataset run was not repeated for the temporary #54347 benchmark stack.

Tasks Version Filter n-shot Metric Value Stderr
gsm8k 3 flexible-extract 5 exact_match 0.9682 ± 0.0048
strict-match 5 exact_match 0.9689 ± 0.0048

The routed output passed the real-checkpoint comparison, and TP8 SP-published shared fragments were bit-exact against dense output.

Compatibility

  • Existing FP8/FP4 MegaMoE calls are unchanged.
  • BF16 shared work, hidden-sharded publication, and SP token publication are independent opt-in modes.
  • Host checks reject incompatible token counts, workspace shapes, dtypes, peer metadata, and devices before launch.
  • The dependent vLLM integration retains its native fallback when the new API or supported topology is unavailable.

Contribution notes

AI assistance was used during implementation, debugging, validation, benchmark analysis, and preparation of this PR description.

static_cast<uint64_t>(current_index) * bytes_per_buffer
+ static_cast<uint64_t>(sym_buffer.rank_idx) * kSharedShard
* sizeof(nv_bfloat16);
shared_column_offset = n_idx - destination_rank * kSharedShard;

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.

🔴 critical: 修正跨分片 tile 的无符号偏移下溢: 当 kSharedHidden / kNumRanks 仅满足当前的 8 元素对齐、但不是 128 元素 tile 对齐时(例如 2304/8=288),一个输出 tile 会跨越目标 rank。跨界 lane 的 destination_rank 已递增,但这里的两个 uint32_t 相减会下溢,随后形成约 8 GiB 的错误地址偏移并执行越界远端写入。应直接根据 vector_n 计算分片内列偏移,或限制分片宽度必须按 BLOCK_N 对齐。

🤖 v6

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.

Fixed in 7c84df6: the RS path now derives the in-shard column from vector_n and removes the separate lane byte offset. This supports shards that are not multiples of BLOCK_N. I also validated the exact crossing case on 8 B200 GPUs with shared_hidden=2304 and shard=288 for all three generations; routed output and every published fragment were bit-exact.

Comment on lines +205 to +341
def _bf16_shared_worker(local_rank: int, master_port: int) -> None:
os.environ['MASTER_ADDR'] = '127.0.0.1'
os.environ['MASTER_PORT'] = str(master_port)
os.environ['WORLD_SIZE'] = '1'
os.environ['RANK'] = '0'

buffer = None
try:
_, _, group = init_dist(local_rank, NUM_RANKS)
generator = torch.Generator(device='cuda')
generator.manual_seed(20260823)

def randn(shape: Tuple[int, ...], scale: float) -> torch.Tensor:
return torch.randn(
shape,
dtype=torch.bfloat16,
device='cuda',
generator=generator).mul_(scale)

routed_x = randn((NUM_TOKENS, HIDDEN), 0.5)
routed_l1 = randn(
(NUM_EXPERTS, INTERMEDIATE * 2, HIDDEN), 0.04)
routed_l2 = randn(
(NUM_EXPERTS, HIDDEN, INTERMEDIATE), 0.04)
shared_x = randn((NUM_TOKENS, SHARED_HIDDEN), 0.25)
shared_l1 = randn(
(SHARED_INTERMEDIATE * 2, SHARED_HIDDEN), 0.02)
shared_l2 = randn(
(SHARED_HIDDEN, SHARED_INTERMEDIATE), 0.02)
rms_weight = randn((HIDDEN,), 0.1).add_(1.0)
rms_epsilon = 1e-6
situ_beta = 1.25
situ_linear_beta = 1.5

routed_x_fp8, routed_x_sf = per_token_cast_to_fp8(
routed_x,
use_ue8m0=True,
gran_k=32,
use_packed_ue8m0=True)
transformed_routed_l1, transformed_routed_l2 = (
deep_gemm.transform_weights_for_mega_moe(
_cast_weights_to_fp4(routed_l1),
_cast_weights_to_fp4(routed_l2),
activation='situ'))
transformed_shared_l1, transformed_shared_l2 = (
deep_gemm.transform_weights_for_mega_moe(
shared_l1, shared_l2, activation='situ'))
topk_idx = (
torch.arange(NUM_TOKENS, device='cuda', dtype=torch.long)
% NUM_EXPERTS
).view(NUM_TOKENS, NUM_TOPK)
topk_weights = torch.ones(
(NUM_TOKENS, NUM_TOPK), device='cuda', dtype=torch.float)

buffer = deep_gemm.get_symm_buffer_for_mega_moe(
group,
NUM_EXPERTS,
NUM_TOKENS,
NUM_TOPK,
HIDDEN,
INTERMEDIATE,
mma_type='fp8xfp4',
activation='situ',
bf16_shared_intermediate_hidden=SHARED_INTERMEDIATE)

def prepare_inputs() -> None:
buffer.buffer.zero_()
buffer.x[:NUM_TOKENS].copy_(routed_x_fp8)
buffer.x_sf[:NUM_TOKENS].copy_(routed_x_sf)
buffer.topk_idx[:NUM_TOKENS].copy_(topk_idx)
buffer.topk_weights[:NUM_TOKENS].copy_(topk_weights)

# Reference routed output: existing routed-only kernel followed by the
# BF16-input RMSNorm contract used by Kimi K3.
prepare_inputs()
routed_unnormalized = torch.empty(
(NUM_TOKENS, HIDDEN), dtype=torch.bfloat16, device='cuda')
deep_gemm.fp8_fp4_mega_moe(
routed_unnormalized,
transformed_routed_l1,
transformed_routed_l2,
buffer,
activation='situ',
situ_beta=situ_beta,
situ_linear_beta=situ_linear_beta)
routed_fp32 = routed_unnormalized.float()
routed_reference = (
routed_fp32
* torch.rsqrt(routed_fp32.square().mean(-1, keepdim=True)
+ rms_epsilon)
* rms_weight.float()).to(torch.bfloat16)

prepare_inputs()
routed_actual = torch.empty_like(routed_unnormalized)
shared_actual = torch.empty(
(NUM_TOKENS, SHARED_HIDDEN),
dtype=torch.bfloat16,
device='cuda')
deep_gemm.fp8_fp4_mega_moe_bf16_shared(
routed_actual,
shared_actual,
transformed_routed_l1,
transformed_routed_l2,
shared_x,
transformed_shared_l1,
transformed_shared_l2,
rms_weight,
rms_epsilon,
buffer,
activation='situ',
situ_beta=situ_beta,
situ_linear_beta=situ_linear_beta)
torch.cuda.synchronize()

gate, up = F.linear(shared_x, shared_l1).chunk(2, dim=-1)
gate_fp32, up_fp32 = gate.float(), up.float()
shared_intermediate = (
torch.sigmoid(gate_fp32)
* (situ_beta * torch.tanh(gate_fp32 / situ_beta))
* (situ_linear_beta
* torch.tanh(up_fp32 / situ_linear_beta))).to(torch.bfloat16)
shared_reference = F.linear(
shared_intermediate, shared_l2).to(torch.bfloat16)

routed_error = _relative_l2(routed_actual, routed_reference)
shared_error = _relative_l2(shared_actual, shared_reference)
if routed_error >= 2e-3:
raise AssertionError(
f'fused RMSNorm relative L2 too high: {routed_error:.6f}')
if shared_error >= 1e-2:
raise AssertionError(
f'BF16 shared expert relative L2 too high: {shared_error:.6f}')
finally:
if buffer is not None:
buffer.destroy()
if dist.is_initialized():
dist.destroy_process_group()

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.

🟡 warning: The in-repo test covers the fused BF16 shared path (fp8_fp4_mega_moe_bf16_shared) but not the RS publication path; per-fragment and consecutive-generation validation was done only in the external test plan. Please add a single-GPU deterministic test of fp8_fp4_mega_moe_bf16_shared_rs (workspace shape (3, tokens, 1, shared_hidden), flags[0] cycling 0..2, peer_ptrs = [workspace.data_ptr()], bit-exact comparison against the dense shared output) so this path is guarded in CI.

🤖 v5

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.

Added a deterministic single-GPU RS regression in 7c84df6. It cycles flags[0] through generations 0, 1, and 2, uses the local workspace pointer as peer_ptrs, and requires bit-exact equality with dense shared output.

Comment thread csrc/apis/mega.hpp
Comment on lines +387 to +394
DG_HOST_ASSERT(shared_rs_flags.is_cuda());
DG_HOST_ASSERT(shared_rs_flags.scalar_type() == torch::kInt);
DG_HOST_ASSERT(shared_rs_flags.is_contiguous() and shared_rs_flags.numel() >= 9);
DG_HOST_ASSERT(shared_rs_peer_ptrs.is_cuda());
DG_HOST_ASSERT(shared_rs_peer_ptrs.scalar_type() == torch::kInt64);
DG_HOST_ASSERT(shared_rs_peer_ptrs.is_contiguous());
DG_HOST_ASSERT(shared_rs_peer_ptrs.dim() == 1);
DG_HOST_ASSERT(shared_rs_peer_ptrs.numel() == static_cast<int64_t>(sym_buffer_ptrs.size()));

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 shared_rs_flags contract (flags[0] = current generation index, flags[2] = bytes per generation buffer, numel >= 9) is only implied by the kernel's __ldg reads and the >=9 host assert. Document this layout in a comment or docstring, since it is a cross-repo contract with the vLLM consumer.

🤖 v5

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.

Documented the cross-repository flags ABI in csrc/apis/mega.hpp in 7c84df6: flags[0] is the generation and flags[2] is the byte stride per generation.

Comment on lines +1581 to +1607
void* shared_output_base = shared_y;
uint64_t shared_output_offset = 0;
uint64_t shared_column_offset = n_idx;
if constexpr (kPublishSharedRS) {
const auto current_index = __ldg(shared_rs_flags);
const auto bytes_per_buffer = __ldg(shared_rs_flags + 2);
constexpr uint32_t kSharedShard =
kSharedHidden / kNumRanks;
const auto vector_n =
n_idx + (lane_idx % 16) * 8;
const auto destination_rank =
vector_n / kSharedShard;
shared_output_base = reinterpret_cast<void*>(
__ldg(shared_rs_peer_ptrs + destination_rank));
shared_output_offset =
static_cast<uint64_t>(current_index) * bytes_per_buffer
+ static_cast<uint64_t>(sym_buffer.rank_idx) * kSharedShard
* sizeof(nv_bfloat16);
shared_column_offset = n_idx - destination_rank * kSharedShard;
}
const auto dst_ptr = math::advance_ptr<float4>(
shared_output_base,
shared_output_offset
+ static_cast<uint64_t>(dst_token_idx) * kSharedHidden * sizeof(nv_bfloat16)
+ shared_column_offset * sizeof(nv_bfloat16)
+ (lane_idx % 16) * sizeof(float4));
*dst_ptr = packed;

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 kernel only reads the flags and never signals completion; cross-rank consumption of published fragments relies entirely on external synchronization in the downstream vLLM tail. State explicitly in the fp8_fp4_mega_moe_bf16_shared_rs docstring that ordering/visibility across ranks is the caller's responsibility.

🤖 v5

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.

Documented caller-owned remote visibility and completion in the Python RS docstring in 7c84df6, and added the same contract next to the kernel publication store in ebef8f1.

Comment on lines +235 to 273
def fp8_fp4_mega_moe_bf16_shared_rs(
y: torch.Tensor,
l1_weights: Tuple[torch.Tensor, torch.Tensor],
l2_weights: Tuple[torch.Tensor, torch.Tensor],
shared_x: torch.Tensor,
shared_l1_weights: torch.Tensor,
shared_l2_weights: torch.Tensor,
rms_weight: torch.Tensor,
rms_epsilon: float,
shared_rs_workspace: torch.Tensor,
shared_rs_flags: torch.Tensor,
shared_rs_peer_ptrs: torch.Tensor,
sym_buffer: SymmBuffer,
cumulative_local_expert_recv_stats: Optional[torch.Tensor] = None,
recipe: Tuple[int, int, int] = (1, 1, 32),
activation: str = 'situ',
fast_math: bool = True,
situ_beta: Optional[float] = None,
situ_linear_beta: Optional[float] = None):
"""Publish BF16 shared outputs into a symmetric ReduceScatter buffer."""
assert sym_buffer.shared_bf16_l2_acts is not None, \
'SymmBuffer was not initialized with a BF16 shared intermediate'
_C.fp8_fp4_mega_moe_bf16_shared(
y, None,
l1_weights, l2_weights,
shared_x, sym_buffer.shared_bf16_l2_acts,
shared_l1_weights, shared_l2_weights,
rms_weight, rms_epsilon,
cumulative_local_expert_recv_stats,
sym_buffer.buffer,
sym_buffer.handle.buffer_ptrs, sym_buffer.group.rank(),
sym_buffer.num_max_tokens_per_rank,
sym_buffer.num_experts, sym_buffer.num_topk,
recipe, activation, fast_math,
situ_beta, situ_linear_beta,
shared_rs_workspace, shared_rs_flags, shared_rs_peer_ptrs
)

def bf16_mega_moe(y: torch.Tensor,

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: fp8_fp4_mega_moe_bf16_shared_rs duplicates the argument marshaling of fp8_fp4_mega_moe_bf16_shared except the shared_y/RS trio; consider a shared private helper to avoid drift. Also missing a blank line before bf16_mega_moe (PEP8 two-blank-line rule).

🤖 v5

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.

Addressed in ebef8f1: both public wrappers now share one private validation/C++ argument-marshaling helper, and the public definitions retain PEP 8 separation.

static_cast<uint64_t>(current_index) * bytes_per_buffer
+ static_cast<uint64_t>(sym_buffer.rank_idx) * kSharedShard
* sizeof(nv_bfloat16);
shared_column_offset = n_idx - destination_rank * kSharedShard;

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.

🔴 critical: shared_column_offset 声明为 uint64_t,但右式 n_idx - destination_rank * kSharedShard 中的量都是 uint32_t,会先按 32 位无符号计算再转为 64 位。当 kSharedShard 不是 BLOCK_N(128) 的倍数时(例如 shared_hidden=1024、num_ranks=16,shard=64;或 shared_hidden=256、num_ranks=4,shard=64),一个 128 列的 N block 会跨越 rank 分片,部分 lane 的 vector_n 落入下一个 destination rank,此时 destination_rank * kSharedShard > n_idx,32 位减法下溢成巨大 uint64 偏移,最终导致 dst_ptr 越界写。建议改为基于 vector_n 计算列内偏移(vector_n - destination_rank * kSharedShard,并移除后续 (lane_idx % 16) * sizeof(float4)),或改用 int64_t 完成减法;同时可在 host/static assert 中增加 kSharedShard % BLOCK_N == 0 的约束。

🤖 v4p

fast_math: bool = True,
situ_beta: Optional[float] = None,
situ_linear_beta: Optional[float] = None):
assert sym_buffer.shared_bf16_l2_acts is not None, \

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.

🟡 warning: 这里只检查 sym_buffer.shared_bf16_l2_acts is not None,但 SymmBuffer 允许同时传入 num_shared_experts > 0bf16_shared_intermediate_hidden > 0。这种 buffer 会通过该检查,而 C++ 侧 fp8_fp4_mega_moe_bf16_shared 固定用 num_shared_experts=0 重新 slice sym_buffer(csrc/apis/mega.hpp:442),导致 routed buffer 视图与实际按 num_shared_experts > 0 分配的布局不一致,内核会读写错误的缓冲区。建议在 SymmBuffer 中记录 num_shared_experts,并在此 assert 其为 0(或让 C++ 按 buffer 的实际布局 slice)。

🤖 v4p

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.

Added an explicit num_shared_experts == 0 guard to both BF16 shared wrappers in 7c84df6, so a routed shared-expert symmetric layout is rejected before entering C++.

@ds-review-bot

Copy link
Copy Markdown
Collaborator

🤖 ds-review-bot Code Review

v6

发布路径对已通过现有校验的部分 shared-hidden/并行度组合会计算出下溢偏移,导致远端显存越界写入。因此该补丁不能视为正确。

v5

The change adds an optional BF16 shared-expert path to the SM100 FP8/FP4 MegaMoE producer (fp8_fp4_mega_moe_bf16_shared) plus a destination-scattered publication variant (fp8_fp4_mega_moe_bf16_shared_rs) that writes each TP-sharded shared partial directly into the destination rank's symmetric workspace, removing the standalone shared-output all-reduce. Correctness verified during review: (1) publication addressing is sound — each lane stores one float4 (8 bf16), vector_n = n_idx + (lane%16)8 selects the per-lane destination rank, the in-shard column resolves to vector_n - destination_rankkSharedShard, and a static assert on (kSharedHidden/kNumRanks) % 8 == 0 guarantees no vector straddles two shards; (2) the host-side workspace contract (dim-4 BF16 tensor, >=3 generations, num_ranks x shard with size(2)size(3) == shared_hidden) matches the kernel's per-token stride kSharedHidden and source-rank offset rank_idxshard; (3) publish_shared_rs is mutually exclusive with a dense shared_y and requires the full workspace/flags/peer_ptrs trio with dtype/contiguity/device checks; (4) static asserts enforce kPublishSharedRS -> kUseBF16Shared -> kNumSharedExperts > 0 and kApplyRMSNorm only with BF16 shared mode; (5) JIT codegen, launch signature, Python bindings, and exports are consistent. Backward compatibility is preserved: new template parameters and host arguments default off, and the scheduler change only lifts SHARED_*_SHAPE/BLOCK_K into template parameters with unchanged defaults, so the existing FP8/FP4 MegaMoE path is untouched.

v4p

MR 为 SM100 FP8/FP4 MegaMoE 增加可选 BF16 shared expert 路径,将 Kimi K3 的 routed experts、BF16 shared expert、RMSNorm 以及 ReduceScatter 发布融合进 MegaMoE 调度,并以 publish 方式避免 shared 输出 TP all-reduce。整体实现基本遵循既有 JIT/调度/TMA 模式,现有 FP8/FP4 路径在非 BF16 shared 场景下保持兼容,测试覆盖了单卡非发布路径;但发布路径存在一个 32 位无符号下溢导致的越界写风险,且 Python 包装对 SymmBuffer 配置缺少互斥校验。

Files reviewed: 7
Issues found: 🔴 2 critical | 🟡 2 warning | 🔵 3 suggestion
Inline comments posted: 7

gcanlin and others added 3 commits August 27, 2026 15:46
Signed-off-by: Canlin Guo <canlinguosdu@gmail.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Canlin Guo <canlinguosdu@gmail.com>
@gcanlin
gcanlin force-pushed the feat/kimi-k3-fp8fp4-bf16-shared branch from 7c6d78f to 7c84df6 Compare August 27, 2026 08:14
@gcanlin

gcanlin commented Aug 27, 2026

Copy link
Copy Markdown
Author

Rebased this PR onto current nv_dev (2642b32, including #409) and resolved the shared MegaMoE API/scheduler conflicts without dropping the new NVFP4 path.

Also addressed the latest review findings in 7c84df6:

  • fixed cross-shard RS addressing for shards not aligned to the 128-column tile;
  • added the three-generation single-GPU RS bit-exact regression;
  • documented the flags ABI and caller-owned publication synchronization;
  • rejected mixed symmetric-buffer shared layouts in the BF16 shared wrappers.

Validation:

  • pytest tests/test_mega_moe_situ.py -v -s: 2 passed on B200;
  • targeted 8-GPU test with shared_hidden=2304, world_size=8, shard=288: all three RS generations bit-exact;
  • real Kimi K3 checkpoint EP8 producer checks at M=32/M=64: routed and destination-scattered shared outputs bit-exact.

I also swept the existing MegaMoE block tiers and an earlier shared-L2 scheduling variant. Neither improved the current heuristic, so those experimental changes were not retained.

Signed-off-by: Canlin Guo <canlinguosdu@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants