2323method-owned MoE kernel references across layerwise reload.
2424"""
2525
26- import copy
2726from types import MethodType
2827from typing import Any
2928
@@ -88,17 +87,14 @@ def _load_modelopt_moe_input_scale(
8887 return True if return_success else None
8988
9089
91- def _normalized_w4a16_config (config : dict [str , Any ]) -> dict [str , Any ]:
92- normalized = copy .deepcopy (config )
93- quantization = normalized .get ("quantization" )
94- target = quantization if isinstance (quantization , dict ) else normalized
90+ def _validated_w4a16_config (config : dict [str , Any ]) -> dict [str , Any ]:
91+ quantization = config .get ("quantization" )
92+ target = quantization if isinstance (quantization , dict ) else config
9593 if str (target .get ("quant_algo" , "" )).upper () != _W4A16_ALGO :
9694 raise ValueError (f"{ NEMO_MODELOPT_W4A16 } requires quant_algo={ _W4A16_ALGO !r} " )
97- # vLLM 0.20 validates known ModelOpt algorithms before dispatching to a
98- # custom subclass. Normalize only for its parser; class identity selects
99- # the W4A16 methods below.
100- target ["quant_algo" ] = _W4A4_ALGO
101- return normalized
95+ # vLLM 0.25 understands W4A16_NVFP4 natively, so the algo passes through
96+ # unchanged (the base __init__ keys use_a16/LinearMethodCls off it).
97+ return config
10298
10399
104100def _canonicalize_nvfp4_scale_ (scale : torch .Tensor ) -> None :
@@ -107,54 +103,6 @@ def _canonicalize_nvfp4_scale_(scale: torch.Tensor) -> None:
107103 scale .copy_ (scale .to (torch .float32 ).abs ().to (scale .dtype ))
108104
109105
110- def _pad_nvfp4_moe_for_marlin (
111- w13 : torch .Tensor ,
112- w13_scale : torch .Tensor ,
113- w2 : torch .Tensor ,
114- w2_scale : torch .Tensor ,
115- * ,
116- is_act_and_mul : bool ,
117- ) -> tuple [torch .Tensor , torch .Tensor , torch .Tensor , torch .Tensor , int ]:
118- """Apply rank-local post-load padding required by the Marlin MoE kernel."""
119- num_experts = w13 .shape [0 ]
120- num_shards = 2 if is_act_and_mul else 1
121- intermediate_size = w13 .shape [1 ] // num_shards
122- hidden_size = w13 .shape [2 ] * 2
123- if hidden_size % 128 == 0 :
124- tile_size = 64
125- elif hidden_size % 64 == 0 :
126- tile_size = 128
127- else :
128- raise ValueError (
129- f"W4A16 Marlin MoE requires hidden_size divisible by 64, got { hidden_size } "
130- )
131- padded_size = (intermediate_size + tile_size - 1 ) // tile_size * tile_size
132- if padded_size == intermediate_size :
133- return w13 , w13_scale , w2 , w2_scale , intermediate_size
134-
135- def pad_w13 (tensor : torch .Tensor ) -> torch .Tensor :
136- tensor = tensor .view (
137- num_experts ,
138- num_shards ,
139- intermediate_size ,
140- tensor .shape [- 1 ],
141- )
142- tensor = torch .nn .functional .pad (
143- tensor ,
144- (0 , 0 , 0 , padded_size - intermediate_size ),
145- )
146- return tensor .reshape (num_experts , num_shards * padded_size , - 1 )
147-
148- w13 = pad_w13 (w13 )
149- w13_scale = pad_w13 (w13_scale )
150- w2 = torch .nn .functional .pad (w2 , (0 , (padded_size - intermediate_size ) // 2 ))
151- w2_scale = torch .nn .functional .pad (
152- w2_scale ,
153- (0 , (padded_size - intermediate_size ) // 16 ),
154- )
155- return w13 , w13_scale , w2 , w2_scale , padded_size
156-
157-
158106def register_nemo_modelopt_nvfp4 () -> None :
159107 """Register NeMo's two ModelOpt NVFP4 configs through vLLM's public API."""
160108 global _registered
@@ -165,14 +113,7 @@ def register_nemo_modelopt_nvfp4() -> None:
165113 MarlinNvFp4LinearKernel ,
166114 NvFp4LinearLayerConfig ,
167115 )
168- from vllm .model_executor .layers .fused_moe .fused_moe_method_base import (
169- FusedMoEMethodBase ,
170- )
171- from vllm .model_executor .layers .fused_moe .oracle .nvfp4 import (
172- NvFp4MoeBackend ,
173- is_global_sf_supported_for_nvfp4_backend ,
174- select_nvfp4_moe_backend ,
175- )
116+ from vllm .model_executor .layers .fused_moe .oracle .nvfp4 import NvFp4MoeBackend
176117 from vllm .model_executor .layers .linear import (
177118 register_weight_loader_v2_supported_method ,
178119 )
@@ -182,8 +123,6 @@ def register_nemo_modelopt_nvfp4() -> None:
182123 ModelOptNvFp4FusedMoE ,
183124 ModelOptNvFp4LinearMethod ,
184125 )
185- from vllm .model_executor .layers .quantization .utils .quant_utils import kNvfp4Static
186- from vllm .model_executor .utils import replace_parameter
187126
188127 class NemoModelOptNvFp4FusedMoE (ModelOptNvFp4FusedMoE ):
189128 """Native W4A4 MoE plus the vLLM 0.20 input-scale loader fix."""
@@ -297,30 +236,17 @@ def apply(
297236 return self .kernel .apply_weights (layer = layer , x = x , bias = bias )
298237
299238 class NemoModelOptW4A16FusedMoE (ModelOptNvFp4FusedMoE ):
300- """ModelOpt W4A16 MoE using vLLM's NVFP4 Marlin implementation."""
239+ """ModelOpt W4A16 MoE using vLLM's NVFP4 Marlin implementation.
240+
241+ vLLM 0.25's base __init__ keys weight-only mode off
242+ quant_config.quant_method == "W4A16_NVFP4" (activation_key=None), so
243+ the 0.20-era duplicated __init__ is gone; the algo passes through
244+ from_config unchanged.
245+ """
301246
302247 moe_kernel : Any
303248 moe_quant_config : Any
304249
305- def __init__ (self , quant_config : object , moe_config : object ) -> None :
306- # Duplicates vLLM v0.20.0 ModelOptNvFp4FusedMoE.__init__ except
307- # activation_key=None (weight-only); the base hard-wires
308- # kNvfp4Dynamic and offers no hook:
309- # https://github.com/vllm-project/vllm/blob/v0.20.0/vllm/model_executor/layers/quantization/modelopt.py#L1218-L1234
310- # Intentionally calls FusedMoEMethodBase.__init__ to skip the
311- # parent __init__; do not replace it with super().__init__().
312- # Re-sync on vLLM bumps.
313- FusedMoEMethodBase .__init__ (self , moe_config )
314- self .quant_config = quant_config
315- self .nvfp4_backend , self .experts_cls = select_nvfp4_moe_backend (
316- config = self .moe ,
317- weight_key = kNvfp4Static ,
318- activation_key = None ,
319- )
320- self .use_global_sf = is_global_sf_supported_for_nvfp4_backend (
321- self .nvfp4_backend
322- )
323-
324250 def create_weights (
325251 self ,
326252 layer : Any ,
@@ -344,35 +270,20 @@ def create_weights(
344270 def process_weights_after_loading (self , layer : Any ) -> None :
345271 reload_kernel = self .moe_kernel
346272 reload_quant_config = self .moe_quant_config
347- original_intermediate_size = (
348- layer .moe_config .intermediate_size_per_partition
349- )
350273 if self .nvfp4_backend == NvFp4MoeBackend .MARLIN :
351- w13 , w13_scale , w2 , w2_scale , padded_size = _pad_nvfp4_moe_for_marlin (
352- layer .w13_weight ,
353- layer .w13_weight_scale ,
354- layer .w2_weight ,
355- layer .w2_weight_scale ,
356- is_act_and_mul = self .moe .is_act_and_mul ,
357- )
358- replace_parameter (layer , "w13_weight" , w13 )
359- replace_parameter (layer , "w13_weight_scale" , w13_scale )
360- replace_parameter (layer , "w2_weight" , w2 )
361- replace_parameter (layer , "w2_weight_scale" , w2_scale )
274+ # vLLM 0.25's prepare_nvfp4_moe_layer_for_marlin pads the
275+ # rank-local intermediate tiles itself (and asserts on the
276+ # unpadded checkpoint shapes), so no NeMo-side pre-padding.
277+ # Only the E4M3 sign-bit canonicalization of the ModelOpt
278+ # export remains our concern.
362279 _canonicalize_nvfp4_scale_ (layer .w13_weight_scale )
363280 _canonicalize_nvfp4_scale_ (layer .w2_weight_scale )
364- layer .moe_config .intermediate_size_per_partition = padded_size
365281 # W4A16 checkpoint metadata deliberately omits activation scales so
366282 # layerwise reload never waits for tensors that do not exist. The
367283 # native Marlin converter accepts None and removes these attributes.
368284 layer .w13_input_scale = None
369285 layer .w2_input_scale = None
370- try :
371- super ().process_weights_after_loading (layer )
372- finally :
373- layer .moe_config .intermediate_size_per_partition = (
374- original_intermediate_size
375- )
286+ super ().process_weights_after_loading (layer )
376287 if reload_kernel is not None :
377288 self .moe_kernel = reload_kernel
378289 self .moe_quant_config = reload_quant_config
@@ -401,7 +312,13 @@ def override_quantization_method(
401312
402313 @classmethod
403314 def from_config (cls , config : dict [str , Any ]) -> Any :
404- return super ().from_config (_normalized_w4a16_config (config ))
315+ instance = super ().from_config (_validated_w4a16_config (config ))
316+ # vLLM 0.25's ModelOptNvFp4Config.__init__ selects LinearMethodCls
317+ # from the quant algo as an *instance* attribute, which shadows
318+ # the class attribute above; rebind the NeMo method explicitly so
319+ # W4A16 linears keep the refit-friendly Marlin implementation.
320+ instance .LinearMethodCls = NemoModelOptW4A16LinearMethod
321+ return instance
405322
406323 register_quantization_config (NEMO_MODELOPT_W4A4 )(NemoModelOptNvFp4Config )
407324 register_quantization_config (NEMO_MODELOPT_W4A16 )(NemoModelOptW4A16Config )
0 commit comments