[Bugfix] Detect unloaded NVFP4 weight scales with a NaN sentinel (#52501)

Signed-off-by: pavelzak <pavel.zakharov@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: OpenAI Codex <codex@openai.com>

Pavel Zakharov committed Sep 13, 2026 at 07:58 UTC b7e0cdac5d11e3a1b32745079cf5591a685aa721
1 file changed +18 -1
vllm/model_executor/layers/quantization/modelopt.py
+18 -1
@@ -1942,7 +1942,9 @@ class KNvfp4Static(QuantKeyScheme):
1942 PerTensorScaleParameter,
1943 wl,
1944 )
1945 - # Per-block (group_size) weight scale.
1945 + # Per-block (group_size) weight scale. Initialized to NaN (rather than
1946 + # left as torch.empty garbage) so an unloaded scale is unambiguously
1947 + # detectable in process() below, matching the sentinel pattern in #45320.
1948 self.register_params(
1949 layer,
1950 "weight_scale",
@@ -1955,11 +1957,26 @@ class KNvfp4Static(QuantKeyScheme):
1957 wl,
1958 input_dim=1,
1959 output_dim=0,
1960 + init=float("nan"),
1961 )
1962
1963 def process(self, layer, role) -> None:
1964 if role is not WEIGHT:
1965 self.reject(role)
1966 + # Sanity-check: weight_scale must have been overwritten by the weight
1967 + # loader. A value still equal to the NaN sentinel it was created with
1968 + # means the FP4 weights were never actually loaded (e.g. the
1969 + # checkpoint stores this layer as BF16 and the weight loader silently
1970 + # skipped it).
1971 + if torch.isnan(layer.weight_scale.float()).any():
1972 + raise RuntimeError(
1973 + f"NVFP4 weight_scale for layer "
1974 + f"{getattr(layer, 'name', repr(layer))!r} was never loaded "
1975 + "(still NaN). The checkpoint likely stores this layer as "
1976 + "BF16 (not FP4). Fix: pass quant_config=None when "
1977 + "constructing this layer, or add it to the quantization "
1978 + "ignore list."
1979 + )
1980 if torch.unique(layer.weight_scale_2).numel() != 1:
1981 logger.warning_once(
1982 "In NVFP4 linear, the global weight scale differs across "