Handle wo_a as bfloat16 (unquantized in NVFP4 checkpoint)

o_a_proj is NOT quantized by modelopt in the checkpoint (bfloat16),
but the attention forward pass expects FP8 (weight + weight_scale_inv).

- Create wo_a with quant_config=None to load bfloat16 weights
- Add FP8 quantization of wo_a in finalize_mega_moe_weights:
  per-tensor symmetric quantization to float8_e4m3fn + weight_scale_inv
- This matches what the fused_inv_rope_fp8_quant + einsum expects
This commit is contained in:
2026-05-18 23:41:39 +00:00
parent 9d016aa1c0
commit e3c24769e2

View File

@@ -989,11 +989,15 @@ class DeepseekV4Attention(nn.Module):
)
self.kv_norm = RMSNorm(self.head_dim, self.eps)
# wo_a is NOT quantized in the NVFP4 checkpoint (modelopt left it as bfloat16),
# but the attention forward pass expects FP8 (weight + weight_scale_inv).
# Pass quant_config=None to load bfloat16, then process_weights_after_loading
# will handle the FP8 quantization.
self.wo_a = ColumnParallelLinear(
self.n_heads * self.head_dim // self.n_groups,
self.n_groups * self.o_lora_rank,
bias=False,
quant_config=quant_config,
quant_config=None,
return_bias=False,
prefix=f"{prefix}.wo_a",
)
@@ -1624,6 +1628,34 @@ class DeepseekV4Model(nn.Module):
def finalize_mega_moe_weights(self) -> None:
for layer in islice(self.layers, self.start_layer, self.end_layer):
layer.ffn.finalize_mega_moe_weights()
# Quantize wo_a to FP8 (checkpoint has bfloat16, forward expects FP8)
attn = layer.self_attn
if hasattr(attn, 'wo_a') and attn.wo_a.weight.dtype == torch.bfloat16:
self._quantize_wo_a_to_fp8(attn.wo_a)
@staticmethod
def _quantize_wo_a_to_fp8(wo_a: ColumnParallelLinear) -> None:
"""Quantize wo_a weight from bfloat16 to float8_e4m3fn.
The attention forward pass (fused_inv_rope_fp8_quant + einsum)
expects wo_a.weight as FP8 and wo_a.weight_scale_inv as float32.
The NVFP4 checkpoint stores wo_a as bfloat16, so we quantize here.
Uses per-tensor symmetric quantization (same as modelopt FP8).
"""
weight_bf16 = wo_a.weight.data
# Per-tensor FP8 quantization: scale = amax / fp8_max
fp8_max = torch.finfo(torch.float8_e4m3fn).max # 448.0
amax = weight_bf16.abs().max().float()
scale = amax / fp8_max
# Avoid division by zero
if scale == 0:
scale = torch.tensor(1.0, device=scale.device)
scale_inv = 1.0 / scale
weight_fp8 = (weight_bf16.float() * scale).to(torch.float8_e4m3fn)
wo_a.weight = torch.nn.Parameter(weight_fp8, requires_grad=False)
wo_a.weight_scale_inv = torch.nn.Parameter(
scale_inv.clone(), requires_grad=False
)
@torch.compile(backend=current_platform.simple_compile_backend)