Skip to content

Commit 94a90b2

Browse files
authored
[model] fix: Dequantize FP8 weights during Ministral3 import and fix model init issues (#2895)
Signed-off-by: Chen Cui <chcui@nvidia.com>
1 parent 20045c3 commit 94a90b2

2 files changed

Lines changed: 55 additions & 4 deletions

File tree

‎src/megatron/bridge/models/ministral3/ministral3_bridge.py‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
Reference: https://huggingface.co/mistralai/Ministral-3-3B-Base-2512
3333
"""
3434

35+
from typing import Mapping, Union
36+
37+
import torch
38+
3539
from megatron.bridge.models.conversion.mapping_registry import MegatronMappingRegistry
3640
from megatron.bridge.models.conversion.model_bridge import MegatronModelBridge
3741
from megatron.bridge.models.conversion.param_mapping import (
@@ -170,6 +174,51 @@ def mapping_registry(self) -> MegatronMappingRegistry:
170174

171175
return MegatronMappingRegistry(*mapping_list)
172176

177+
def maybe_modify_loaded_hf_weight(
178+
self,
179+
hf_param: Union[str, dict[str, str]],
180+
hf_state_dict: Mapping[str, torch.Tensor],
181+
) -> Union[torch.Tensor, dict[str, torch.Tensor]]:
182+
"""Load HF weights and dequantize FP8 tensors on the fly.
183+
184+
Ministral-3-*-Instruct-2512 stores LM weights in FP8 (float8_e4m3fn) with
185+
separate ``weight_scale_inv`` scalar tensors. The true bfloat16 weight is::
186+
187+
w_bf16 = fp8_weight.to(bfloat16) * weight_scale_inv
188+
189+
This override applies dequantization transparently so that the bridge produces
190+
correct Megatron checkpoints without a separate preprocessing step.
191+
"""
192+
hf_weights = super().maybe_modify_loaded_hf_weight(hf_param, hf_state_dict)
193+
194+
if isinstance(hf_weights, dict):
195+
# Compound params (QKV / GatedMLP): dequantize each component individually
196+
return {
197+
key: self._maybe_dequantize_fp8(tensor, hf_param[key], hf_state_dict)
198+
for key, tensor in hf_weights.items()
199+
}
200+
return self._maybe_dequantize_fp8(hf_weights, hf_param, hf_state_dict)
201+
202+
@staticmethod
203+
def _maybe_dequantize_fp8(
204+
weight: torch.Tensor,
205+
param_name: str,
206+
hf_state_dict: Mapping[str, torch.Tensor],
207+
) -> torch.Tensor:
208+
"""Dequantize *weight* if it is stored as FP8.
209+
210+
Looks up ``param_name + "_scale_inv"`` in *hf_state_dict* and applies::
211+
212+
w_bf16 = weight.to(bfloat16) * scale_inv
213+
"""
214+
if weight.dtype != torch.float8_e4m3fn:
215+
return weight
216+
scale_key = param_name + "_scale_inv"
217+
if scale_key not in hf_state_dict:
218+
return weight.to(torch.bfloat16)
219+
scale_inv = hf_state_dict[scale_key].to(torch.bfloat16)
220+
return weight.to(torch.bfloat16) * scale_inv
221+
173222

174223
# Register the bridge if Mistral3ForConditionalGeneration is available
175224
if HAS_MISTRAL3 and Mistral3ForConditionalGeneration is not None:

‎src/megatron/bridge/models/ministral3/modeling_ministral3.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,10 @@ def _apply_preserve_inv_freq(fn):
140140
# Apply the transformation (e.g., bfloat16 conversion)
141141
result = original_apply(fn)
142142

143-
# Restore inv_freq to FP32 (only move device, keep dtype)
143+
# Restore inv_freq to FP32 but on the correct device
144144
if inv_freq_backup is not None:
145-
# Get the new device from the transformation
146-
new_device = fn(torch.tensor([1.0])).device
147-
pos_emb.inv_freq.data = inv_freq_backup.to(device=new_device)
145+
target_device = pos_emb.inv_freq.data.device
146+
pos_emb.inv_freq.data = inv_freq_backup.to(device=target_device)
148147

149148
return result
150149

@@ -177,6 +176,9 @@ def _apply_preserve_inv_freq(fn):
177176
# Some config requires from HF vision tower
178177
self.config.spatial_merge_size = getattr(self.config.hf_config, "spatial_merge_size", 2)
179178
self.config.vision_feature_layer = getattr(self.config.hf_config, "vision_feature_layer", -1)
179+
# HF's get_image_features accesses self.config.return_dict
180+
if not hasattr(self.config, "return_dict"):
181+
self.config.return_dict = True
180182

181183
def set_input_tensor(self, input_tensor) -> None:
182184
"""Set model chunk input tensor."""

0 commit comments

Comments
 (0)