@@ -518,3 +518,108 @@ def build(self, module, config):
518518 module = CausalLMModel (config )
519519 pkg = build_from_module (module , config , task = StubTask ())
520520 assert pkg ["model" ].graph .name == "stub"
521+
522+
523+ class TestDequantizeFP8Weights :
524+ """Tests for _dequantize_fp8_weights."""
525+
526+ def test_no_fp8_returns_unchanged (self ):
527+ """Non-FP8 state dicts pass through unchanged."""
528+ from mobius ._weight_loading import _dequantize_fp8_weights
529+
530+ state_dict = {
531+ "layer.weight" : torch .randn (4 , 4 ),
532+ "layer.bias" : torch .randn (4 ),
533+ }
534+ result = _dequantize_fp8_weights (state_dict )
535+ assert set (result .keys ()) == set (state_dict .keys ())
536+ assert torch .equal (result ["layer.weight" ], state_dict ["layer.weight" ])
537+
538+ def test_fp8_e4m3fn_dequantized (self ):
539+ """FP8 weights are multiplied by weight_scale_inv."""
540+ from mobius ._weight_loading import _dequantize_fp8_weights
541+
542+ fp8_weight = torch .tensor ([1.0 , 2.0 , - 1.0 , 0.5 ], dtype = torch .float32 ).to (
543+ torch .float8_e4m3fn
544+ )
545+ scale_inv = torch .tensor (0.5 , dtype = torch .bfloat16 )
546+ state_dict = {
547+ "proj.weight" : fp8_weight ,
548+ "proj.weight_scale_inv" : scale_inv ,
549+ }
550+ result = _dequantize_fp8_weights (state_dict )
551+ assert "proj.weight" in result
552+ assert "proj.weight_scale_inv" not in result # aux tensor removed
553+ assert result ["proj.weight" ].dtype == torch .bfloat16
554+ # Verify dequant: fp8→bf16 * scale_inv
555+ expected = fp8_weight .to (torch .bfloat16 ) * scale_inv
556+ assert torch .allclose (result ["proj.weight" ], expected )
557+
558+ def test_activation_scale_removed (self ):
559+ """Auxiliary activation_scale tensors are removed."""
560+ from mobius ._weight_loading import _dequantize_fp8_weights
561+
562+ state_dict = {
563+ "proj.weight" : torch .tensor ([1.0 ], dtype = torch .float32 ).to (torch .float8_e4m3fn ),
564+ "proj.weight_scale_inv" : torch .tensor (1.0 , dtype = torch .bfloat16 ),
565+ "proj.activation_scale" : torch .tensor (1.0 , dtype = torch .bfloat16 ),
566+ }
567+ result = _dequantize_fp8_weights (state_dict )
568+ assert "proj.activation_scale" not in result
569+
570+ def test_suffix_replace_not_greedy (self ):
571+ """The scale key derivation uses suffix replacement, not global replace.
572+
573+ For a key like 'model.weight_proj.weight', the scale key should be
574+ 'model.weight_proj.weight_scale_inv' (not 'model.weight_scale_inv_proj.weight_scale_inv').
575+ """
576+ from mobius ._weight_loading import _dequantize_fp8_weights
577+
578+ fp8_weight = torch .tensor ([1.0 ], dtype = torch .float32 ).to (torch .float8_e4m3fn )
579+ scale = torch .tensor (2.0 , dtype = torch .bfloat16 )
580+ state_dict = {
581+ "model.weight_proj.weight" : fp8_weight ,
582+ "model.weight_proj.weight_scale_inv" : scale ,
583+ }
584+ result = _dequantize_fp8_weights (state_dict )
585+ assert "model.weight_proj.weight" in result
586+ assert result ["model.weight_proj.weight" ].dtype == torch .bfloat16
587+
588+ def test_missing_scale_casts_without_scaling (self ):
589+ """FP8 weight without scale_inv is cast to bfloat16 without scaling."""
590+ from mobius ._weight_loading import _dequantize_fp8_weights
591+
592+ fp8_weight = torch .tensor ([1.0 , 2.0 ], dtype = torch .float32 ).to (torch .float8_e4m3fn )
593+ state_dict = {"orphan.weight" : fp8_weight }
594+ result = _dequantize_fp8_weights (state_dict )
595+ assert result ["orphan.weight" ].dtype == torch .bfloat16
596+
597+ def test_fp32_scale_produces_bf16_output (self ):
598+ """FP32 weight_scale_inv should still produce bfloat16 output."""
599+ from mobius ._weight_loading import _dequantize_fp8_weights
600+
601+ fp8_weight = torch .tensor ([1.0 , 2.0 ], dtype = torch .float32 ).to (torch .float8_e4m3fn )
602+ # Scale stored as FP32 (common for scalar scales in real checkpoints)
603+ scale_inv = torch .tensor (0.5 , dtype = torch .float32 )
604+ state_dict = {
605+ "proj.weight" : fp8_weight ,
606+ "proj.weight_scale_inv" : scale_inv ,
607+ }
608+ result = _dequantize_fp8_weights (state_dict )
609+ assert result ["proj.weight" ].dtype == torch .bfloat16 , (
610+ f"Expected bfloat16, got { result ['proj.weight' ].dtype } "
611+ )
612+
613+ def test_does_not_mutate_input (self ):
614+ """_dequantize_fp8_weights should not mutate the input dict."""
615+ from mobius ._weight_loading import _dequantize_fp8_weights
616+
617+ fp8_weight = torch .tensor ([1.0 ], dtype = torch .float32 ).to (torch .float8_e4m3fn )
618+ scale = torch .tensor (1.0 , dtype = torch .bfloat16 )
619+ original = {
620+ "proj.weight" : fp8_weight ,
621+ "proj.weight_scale_inv" : scale ,
622+ }
623+ original_keys = set (original .keys ())
624+ _dequantize_fp8_weights (original )
625+ assert set (original .keys ()) == original_keys , "Input dict was mutated"
0 commit comments