A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 11, 21:15 UTC
Add latent MoE support (#30203)
Signed-off-by: Shahar Mor <smor@nvidia.com> Co-authored-by: Tyler Michael Smith <tyler@neuralmagic.com>
shaharmor98 committed
Dec 8, 2025 at 19:35 UTC
fcd5306f65876f72122d7e5852b6000738498d7e
2 files changed
+58
-6
vllm/model_executor/models/nemotron_h.py
+56
-6
@@ -83,6 +83,7 @@ class NemotronHMLP(nn.Module):
83
def __init__(
84
self,
85
config: NemotronHConfig,
86
+ hidden_size: int,
87
intermediate_size: int,
88
quant_config: QuantizationConfig | None = None,
89
bias: bool = False,
@@ -93,7 +94,7 @@ class NemotronHMLP(nn.Module):
94
super().__init__()
95
96
self.up_proj = ColumnParallelLinear(
96
- input_size=config.hidden_size,
97
+ input_size=hidden_size,
98
output_size=intermediate_size,
99
bias=bias,
100
quant_config=quant_config,
@@ -102,7 +103,7 @@ class NemotronHMLP(nn.Module):
103
)
104
self.down_proj = RowParallelLinear(
105
input_size=intermediate_size,
105
- output_size=config.hidden_size,
106
+ output_size=hidden_size,
107
bias=bias,
108
quant_config=quant_config,
109
reduce_results=reduce_results,
@@ -135,6 +136,10 @@ class NemotronHMoE(nn.Module):
136
self.ep_size = self.ep_group.size()
137
self.n_routed_experts: int = config.n_routed_experts
138
self.n_shared_experts: int = config.n_shared_experts
139
+ self.use_latent_moe: bool = getattr(config, "moe_latent_size", None) is not None
140
+ self.moe_hidden_size: int = (
141
+ config.moe_latent_size if self.use_latent_moe else config.hidden_size
142
+ )
143
144
self.is_sequence_parallel = parallel_config.use_sequence_parallel_moe
145
@@ -172,6 +177,7 @@ class NemotronHMoE(nn.Module):
177
178
self.shared_experts = NemotronHMLP(
179
config=config,
180
+ hidden_size=config.hidden_size,
181
intermediate_size=intermediate_size,
182
quant_config=quant_config,
183
reduce_results=False,
@@ -180,10 +186,12 @@ class NemotronHMoE(nn.Module):
186
)
187
188
self.experts = SharedFusedMoE(
183
- shared_experts=self.shared_experts,
189
+ # TODO: make it possible for shared experts to have
190
+ # different input in SharedFusedMoE
191
+ shared_experts=self.shared_experts if not self.use_latent_moe else None,
192
num_experts=config.n_routed_experts,
193
top_k=config.num_experts_per_tok,
186
- hidden_size=config.hidden_size,
194
+ hidden_size=self.moe_hidden_size,
195
intermediate_size=config.moe_intermediate_size,
196
reduce_results=False,
197
renormalize=config.norm_topk_prob,
@@ -201,6 +209,32 @@ class NemotronHMoE(nn.Module):
209
is_sequence_parallel=self.is_sequence_parallel,
210
)
211
212
+ if self.use_latent_moe:
213
+ # TODO: check if using ReplicatedLinear is better than
214
+ # ColumnParallelLinear + all_gather
215
+ self.fc1_latent_proj = ColumnParallelLinear(
216
+ input_size=config.hidden_size,
217
+ output_size=self.moe_hidden_size,
218
+ bias=config.mlp_bias,
219
+ quant_config=quant_config,
220
+ disable_tp=self.is_sequence_parallel,
221
+ # We need to gather the output to prepare input for moe
222
+ gather_output=True,
223
+ prefix=f"{prefix}.fc1_latent_proj",
224
+ )
225
+ self.fc2_latent_proj = ReplicatedLinear(
226
+ input_size=self.moe_hidden_size,
227
+ output_size=config.hidden_size,
228
+ bias=config.mlp_bias,
229
+ quant_config=quant_config,
230
+ disable_tp=self.is_sequence_parallel,
231
+ prefix=f"{prefix}.fc2_latent_proj",
232
+ )
233
+
234
+ else:
235
+ self.fc1_latent_proj = None
236
+ self.fc2_latent_proj = None
237
+
238
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
239
num_tokens, hidden_dim = hidden_states.shape
240
hidden_states = hidden_states.view(-1, hidden_dim)
@@ -210,12 +244,20 @@ class NemotronHMoE(nn.Module):
244
245
# router_logits: (num_tokens, n_experts)
246
router_logits, _ = self.gate(hidden_states.to(dtype=torch.float32))
247
+ shared_output = None
248
+ if self.use_latent_moe:
249
+ if self.shared_experts is not None:
250
+ shared_output = self.shared_experts(hidden_states)
251
+ hidden_states, _ = self.fc1_latent_proj(hidden_states)
252
253
fused_moe_out = self.experts(
254
hidden_states=hidden_states, router_logits=router_logits
255
)
256
218
- shared_output, final_hidden_states = fused_moe_out
257
+ if self.use_latent_moe:
258
+ _, final_hidden_states = fused_moe_out
259
+ else:
260
+ shared_output, final_hidden_states = fused_moe_out
261
262
# Fix FP16 overflow
263
# See DeepseekV2DecoderLayer for more details.
@@ -225,6 +267,13 @@ class NemotronHMoE(nn.Module):
267
assert shared_output is not None
268
shared_output *= 1.0 / self.routed_scaling_factor
269
270
+ # TODO: currently latent up_proj is done before all-reduce for simplicity.
271
+ # if and when shared experts will be part of SharedFusedMoE,
272
+ # we should do the up_proj after all-reduce,
273
+ # to have the all-reduce in the smaller latent dimension.
274
+ if self.use_latent_moe:
275
+ final_hidden_states, _ = self.fc2_latent_proj(final_hidden_states)
276
+
277
if self.shared_experts is not None:
278
assert shared_output is not None
279
final_hidden_states += shared_output
@@ -268,6 +317,7 @@ class NemotronHMLPDecoderLayer(nn.Module):
317
318
self.mixer = NemotronHMLP(
319
config,
320
+ hidden_size=config.hidden_size,
321
intermediate_size=intermediate_size,
322
quant_config=quant_config,
323
bias=config.mlp_bias,
@@ -846,5 +896,5 @@ class NemotronHForCausalLM(
896
return logits
897
898
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
849
- loader = AutoWeightsLoader(self)
899
+ loader = AutoWeightsLoader(self, skip_prefixes=["mtp"])
900
return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
vllm/transformers_utils/configs/nemotron_h.py
+2
@@ -189,6 +189,7 @@ class NemotronHConfig(PretrainedConfig):
189
n_shared_experts=1,
190
moe_intermediate_size=7688,
191
moe_shared_expert_intermediate_size=7688,
192
+ moe_latent_size=None,
193
num_experts_per_tok=2,
194
routed_scaling_factor=1.0,
195
n_group=1,
@@ -254,6 +255,7 @@ class NemotronHConfig(PretrainedConfig):
255
self.n_shared_experts = n_shared_experts
256
self.moe_intermediate_size = moe_intermediate_size
257
self.moe_shared_expert_intermediate_size = moe_shared_expert_intermediate_size # noqa: E501
258
+ self.moe_latent_size = moe_latent_size
259
self.num_experts_per_tok = num_experts_per_tok
260
self.routed_scaling_factor = routed_scaling_factor
261
self.n_group = n_group