Shard the K3 Latent-MoE up-projection on large batches (#50383)
Jee Jee Li committed
Aug 3, 2026 at 15:49 UTC
4635cc3e8f609755ad45f9fc2cdb54fa7552038e
2 files changed
+176
-72
vllm/model_executor/layers/fused_moe/runner/latent_moe_runner.py
+174
-58
@@ -1,42 +1,82 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from enum import IntEnum
4
+
5
import torch
6
7
import vllm.envs as envs
8
from vllm.config import get_current_vllm_config
7
-from vllm.distributed import tensor_model_parallel_all_reduce
9
+from vllm.distributed import (
10
+ get_tensor_model_parallel_rank,
11
+ tensor_model_parallel_all_reduce,
12
+)
13
from vllm.logger import init_logger
14
from vllm.model_executor.layers.layernorm import RMSNorm
10
-from vllm.utils.torch_utils import aux_stream, current_stream
15
+from vllm.platforms import current_platform
16
+from vllm.utils.multi_stream_utils import maybe_execute_in_parallel
17
+from vllm.utils.torch_utils import aux_stream
18
19
from .moe_runner import MoERunner, _unpack
20
21
logger = init_logger(__name__)
22
23
24
+class LatentTailTier(IntEnum):
25
+ """Which tail implementation the fused path runs, by token count.
26
+
27
+ The tiers share the same replicated up-projection weight, so the choice is
28
+ per batch and needs no weight relayout: tiers 0 and 2 read it as a
29
+ column-parallel row-shard, tier 1 reads it whole.
30
+ """
31
+
32
+ # ``_small_batch_tail``. Decode-sized (<= the op's max_num_tokens): one
33
+ # CuTeDSL collective fuses the latent reduce, RMSNorm and the shared
34
+ # reduce-scatter, then a sharded up-projection multicasts through a Lamport
35
+ # copy. Requires SM100, TP 8/16, BF16
36
+ TAIL_FUSION = 0
37
+
38
+ # ``_overlap_allreduce_tail``. The portable default, up to
39
+ # VLLM_SHARED_EXPERTS_STREAM_TOKEN_THRESHOLD tokens: reduce the latent,
40
+ # up-project the full hidden dim from the replicated weight, and add the
41
+ # separately reduced shared output, hiding that shared all-reduce behind the
42
+ # up-projection GEMM on the aux stream.
43
+ ALLREDUCE_OVERLAP = 1
44
+
45
+ # ``_shard_up_proj_tail``. Prefill-sized: each rank up-projects only its
46
+ # hidden shard and accumulates into the shared partial, so the shared
47
+ # all-reduce also stitches the routed shards. Same two all-reduces as tier 1
48
+ # at 1/tp of the up-projection FLOPs; it gives up tier 1's aux-stream
49
+ # overlap, since the reduce now has to follow the accumulate.
50
+ COLUMN_PARALLEL = 2
51
+
52
+
53
class LatentMoERunner(MoERunner):
54
"""MoE runner for latent MoE with a replicated routed up-projection.
55
20
- Fused path (tp>1, un-reduced combine output, shared expert, no SP):
21
- concatenates the un-reduced latent partial (dim d) and the un-reduced
22
- shared partial (dim D) into one contiguous buffer, all-reduces once, then
23
- splits. The latent half is normed and up-projected locally (replicated
24
- up-proj -> full hidden), and the shared add folds into the GEMM epilogue
25
- (``torch.addmm``). One collective total, no post-reduction communication.
56
+ The fused path (tp>1, un-reduced combine output, shared expert, no SP)
57
+ dispatches over ``LatentTailTier`` by token count; see that enum for what
58
+ each tier does and when it applies.
59
60
Native path: the replicated up-proj produces the full hidden dim on every
28
- rank, so the base runner combines routed + shared correctly at any TP size
29
- (using two collectives instead of the fused path's one).
61
+ rank, so the base runner combines routed + shared correctly at any TP size.
62
"""
63
64
def __init__(
65
self,
66
*args,
35
- enable_k3_latent_moe_tail_fusion: bool = False,
67
**kwargs,
68
) -> None:
69
super().__init__(*args, **kwargs)
39
- self.enable_k3_latent_moe_tail_fusion = enable_k3_latent_moe_tail_fusion
70
+
71
+ # The tail-fusion kernels are tcgen05-based, so they require an
72
+ # SM100 NVIDIA device; the runner falls back to the default latent
73
+ # MoE path everywhere else.
74
+ self.enable_k3_latent_moe_tail_fusion = (
75
+ current_platform.is_cuda()
76
+ and current_platform.is_device_capability_family(100)
77
+ )
78
+ # Overlap the shared-expert all-reduce with the tier-1 up-projection.
79
+ self._shared_ar_events = (torch.cuda.Event(), torch.cuda.Event())
80
use_fused_path = self._use_fused_path()
81
if (
82
self.enable_k3_latent_moe_tail_fusion
@@ -113,6 +153,121 @@ class LatentMoERunner(MoERunner):
153
and not self.moe_config.is_sequence_parallel
154
)
155
156
+ def _select_tail_tier(
157
+ self,
158
+ fused_output: torch.Tensor,
159
+ shared_output: torch.Tensor,
160
+ ) -> LatentTailTier:
161
+ num_tokens = fused_output.shape[0]
162
+ # tier 0
163
+ if self.enable_k3_latent_moe_tail_fusion and (
164
+ 0 < num_tokens <= self._k3_latent_moe_tail_op.contract.max_num_tokens
165
+ ):
166
+ return LatentTailTier.TAIL_FUSION
167
+
168
+ transform = self.routed_output_transform
169
+ assert transform is not None
170
+ # tier 1
171
+ if (
172
+ num_tokens <= envs.VLLM_SHARED_EXPERTS_STREAM_TOKEN_THRESHOLD
173
+ and not envs.VLLM_DISABLE_SHARED_EXPERTS_STREAM
174
+ ):
175
+ return LatentTailTier.ALLREDUCE_OVERLAP
176
+ # tier 2
177
+ return LatentTailTier.COLUMN_PARALLEL
178
+
179
+ def _small_batch_tail(
180
+ self,
181
+ fused_output: torch.Tensor,
182
+ shared_output: torch.Tensor,
183
+ trunc_size: int | None,
184
+ ) -> torch.Tensor:
185
+ """Tier 0: the CuTeDSL operator fuses the whole tail."""
186
+ transform = self.routed_output_transform
187
+ assert transform is not None
188
+ norm = transform.norm
189
+ assert norm is not None
190
+
191
+ result = self._k3_latent_moe_tail_op(
192
+ fused_output,
193
+ shared_output,
194
+ norm.weight,
195
+ transform.up_proj.weight,
196
+ )
197
+ # The operator already reduced; this only strips padding.
198
+ return self._maybe_reduce_final_output(
199
+ result, trunc_size, output_is_reduced=True
200
+ )
201
+
202
+ def _overlap_allreduce_tail(
203
+ self,
204
+ fused_output: torch.Tensor,
205
+ shared_output: torch.Tensor,
206
+ trunc_size: int | None,
207
+ ) -> torch.Tensor:
208
+ """Tier 1: reduce the latent, up-project the full hidden dim from the
209
+ replicated weight, and add the separately reduced shared output.
210
+
211
+ Small enough batches hide that shared all-reduce behind the up-projection
212
+ GEMM on the aux stream.
213
+ """
214
+ transform = self.routed_output_transform
215
+ assert transform is not None
216
+ assert shared_output.size(0) <= envs.VLLM_SHARED_EXPERTS_STREAM_TOKEN_THRESHOLD
217
+ if transform.norm is not None:
218
+ fused_latent = self.allreduce_norm_latent_out(fused_output, transform.norm)
219
+ else:
220
+ fused_latent = tensor_model_parallel_all_reduce(fused_output)
221
+
222
+ # Overlap the shared-expert all-reduce with the up-projection GEMM while
223
+ # the batch is small enough for it to pay off.
224
+ result, shared_output = maybe_execute_in_parallel(
225
+ lambda: torch.mm(fused_latent, transform.up_proj.weight.t()),
226
+ lambda: tensor_model_parallel_all_reduce(shared_output),
227
+ self._shared_ar_events[0],
228
+ self._shared_ar_events[1],
229
+ aux_stream(),
230
+ )
231
+ result.add_(shared_output)
232
+
233
+ # Output is already fully reduced; this only strips padding.
234
+ return self._maybe_reduce_final_output(
235
+ result, trunc_size, output_is_reduced=True
236
+ )
237
+
238
+ def _shard_up_proj_tail(
239
+ self,
240
+ fused_output: torch.Tensor,
241
+ shared_output: torch.Tensor,
242
+ trunc_size: int | None,
243
+ ) -> torch.Tensor:
244
+ """
245
+ Tier 2: column-parallel up-projection folded into the final reduce.
246
+ """
247
+ transform = self.routed_output_transform
248
+ assert transform is not None
249
+
250
+ if transform.norm is not None:
251
+ latent = self.allreduce_norm_latent_out(fused_output, transform.norm)
252
+ else:
253
+ latent = tensor_model_parallel_all_reduce(fused_output)
254
+
255
+ weight = transform.up_proj.weight
256
+ shard_size = weight.shape[0] // self.moe_config.tp_size
257
+ shard_start = get_tensor_model_parallel_rank() * shard_size
258
+
259
+ # column-parallel
260
+ up_proj_shard = weight.narrow(0, shard_start, shard_size)
261
+ hidden_shard = shared_output.narrow(-1, shard_start, shard_size)
262
+
263
+ # hidden_shard += latent @ up_proj_shard.T, accumulated in the GEMM's
264
+ # beta-add epilogue so folding in the shared partial costs no kernel.
265
+ hidden_shard.addmm_(latent, up_proj_shard.t())
266
+
267
+ return self._maybe_reduce_final_output(
268
+ shared_output, trunc_size, output_is_reduced=False
269
+ )
270
+
271
def forward(
272
self,
273
hidden_states: torch.Tensor,
@@ -167,54 +322,15 @@ class LatentMoERunner(MoERunner):
322
if og_hidden_dim_pre_xform is not None:
323
fused_output = fused_output[..., :og_hidden_dim_pre_xform]
324
170
- transform = self.routed_output_transform
171
- assert transform is not None
172
-
173
- if self.enable_k3_latent_moe_tail_fusion:
174
- op = self._k3_latent_moe_tail_op
175
- if 0 < fused_output.shape[0] <= op.contract.max_num_tokens:
176
- norm = transform.norm
177
- assert norm is not None
178
- result = op(
179
- fused_output,
180
- shared_output,
181
- norm.weight,
182
- transform.up_proj.weight,
183
- )
184
- result = self._maybe_reduce_final_output(
185
- result, og_hidden_dim_post_xform, output_is_reduced=True
186
- )
187
- return self._maybe_add_zero_expert_output(result)
188
-
189
- fused_latent = None
190
- if transform.norm is not None:
191
- fused_latent = self.allreduce_norm_latent_out(fused_output, transform.norm)
325
+ tier = self._select_tail_tier(fused_output, shared_output)
326
+ if tier is LatentTailTier.TAIL_FUSION:
327
+ latent_tail = self._small_batch_tail
328
+ elif tier is LatentTailTier.ALLREDUCE_OVERLAP:
329
+ latent_tail = self._overlap_allreduce_tail
330
else:
193
- fused_latent = tensor_model_parallel_all_reduce(fused_output)
331
+ latent_tail = self._shard_up_proj_tail
332
195
- shared_expert_stream = (
196
- aux_stream()
197
- if shared_output.size(0) <= envs.VLLM_SHARED_EXPERTS_STREAM_TOKEN_THRESHOLD
198
- else None
199
- )
200
- if shared_expert_stream is not None:
201
- # overlap shared expert allreduce with latent up_proj
202
- main = current_stream()
203
- shared_output.record_stream(shared_expert_stream)
204
- shared_expert_stream.wait_stream(main)
205
- with torch.cuda.stream(shared_expert_stream):
206
- shared_output = tensor_model_parallel_all_reduce(shared_output)
207
- result = torch.mm(fused_latent, transform.up_proj.weight.t())
208
- main.wait_stream(shared_expert_stream)
209
- else:
210
- shared_output = tensor_model_parallel_all_reduce(shared_output)
211
- result = torch.mm(fused_latent, transform.up_proj.weight.t())
212
- result.add_(shared_output)
213
-
214
- # Output is already fully reduced; this only strips padding.
215
- result = self._maybe_reduce_final_output(
216
- result, og_hidden_dim_post_xform, output_is_reduced=True
217
- )
333
+ result = latent_tail(fused_output, shared_output, og_hidden_dim_post_xform)
334
335
return self._maybe_add_zero_expert_output(result)
336
vllm/models/kimi_k3/nvidia/model.py
+2
-14
@@ -472,8 +472,8 @@ class KimiMoE(nn.Module):
472
if self.num_shared_experts is not None:
473
shared_intermediate_size = moe_intermediate_size * self.num_shared_experts
474
self.shared_experts = KimiMLP(
475
- hidden_size=config.hidden_size,
476
- intermediate_size=shared_intermediate_size,
475
+ hidden_size=config.hidden_size, # 7618
476
+ intermediate_size=shared_intermediate_size, # 3072*2
477
hidden_act=config.hidden_act,
478
quant_config=quant_config,
479
reduce_results=False,
@@ -553,13 +553,6 @@ class KimiMoE(nn.Module):
553
activation_linear_beta=activation_situ_linear_beta,
554
)
555
else:
556
- # The tail-fusion kernels are tcgen05-based, so they require an
557
- # SM100 NVIDIA device; the runner falls back to the default latent
558
- # MoE path everywhere else.
559
- enable_tail_fusion = (
560
- current_platform.is_cuda()
561
- and current_platform.is_device_capability_family(100)
562
- )
556
self.experts = FusedMoEFactory(
557
shared_experts=self.shared_experts,
558
num_experts=num_experts,
@@ -586,11 +579,6 @@ class KimiMoE(nn.Module):
579
routed_output_transform=self.routed_output_transform,
580
is_sequence_parallel=use_sequence_parallel,
581
runner_cls=LatentMoERunner if self.use_latent_moe else None,
589
- runner_args=(
590
- {"enable_k3_latent_moe_tail_fusion": enable_tail_fusion}
591
- if self.use_latent_moe
592
- else None
593
- ),
582
)
583
if self.padded_moe_intermediate_size != moe_intermediate_size:
584
w13_weight = getattr(self.experts, "w13_weight", None)