已合并
[feature]FA MXFP8 特性提交,新注册标识字符串“MXFP8_DYNAMIC”,并同步提交量化代码类 #375
乐加维林创建于 6月22日
[feature]FA MXFP8 特性提交,新注册标识字符串“MXFP8_DYNAMIC”,并同步提交量化代码类 #375
已合并
乐加维林创建于 6月22日
4 个文件变更+87-0
Mmindiesd/quantization/layer.py+70-0
@@ -494,6 +494,76 @@ class FP8RotateQuantFA(nn.Module):
494 return x494 return x
495 495 
496 496 
497+class MXFP8RotateQuantFA(nn.Module):
498+ def __init__(self, prefix=None, weights=None):
499+ super().__init__()
500+ 
501+ q_rot = get_quant_weight(weights, f'{prefix}.q_rot')
502+ self.register_buffer("q_rot", q_rot, persistent=False)
503+ k_rot = get_quant_weight(weights, f'{prefix}.k_rot')
504+ self.register_buffer("k_rot", k_rot, persistent=False)
505+ 
506+ def forward(self, query, key, value, **kwargs):
507+ query = torch.matmul(query, self.q_rot)
508+ key = torch.matmul(key, self.k_rot)
509+ 
510+ layout = kwargs.get("layout", "BNSD")
511+ if layout == "BNSD":
512+ b, n, s, d = query.shape
513+ query = query.permute(0, 2, 1, 3).reshape(b * s, n, d)
514+ key = key.permute(0, 2, 1, 3).reshape(b * s, n, d)
515+ value = value.permute(0, 2, 1, 3).reshape(b * s, n, d)
516+ elif layout == "BSND":
517+ b, s, n, d = query.shape
518+ query = query.reshape(b * s, n, d)
519+ key = key.reshape(b * s, n, d)
520+ value = value.reshape(b * s, n, d)
521+ else:
522+ raise ValueError(f"Unsupported layout: {layout}, expected 'BNSD' or 'BSND'.")
523+ 
524+ actual_seq_qlen = torch.arange(s, s * (b + 1), s, dtype=torch.int64, device=query.device)
525+ actual_seq_kvlen = torch.arange(s, s * (b + 1), s, dtype=torch.int64, device=key.device)
526+ 
527+ q, q_scale = torch_npu.npu_dynamic_mx_quant(query, dst_type=torch.float8_e4m3fn, axis=-1)
528+ k, k_scale = torch_npu.npu_dynamic_mx_quant(key, dst_type=torch.float8_e4m3fn, axis=-1)
529+ v, v_scale = torch_npu.npu_dynamic_mx_quant(value, dst_type=torch.float8_e4m3fn, axis=0)
530+ 
531+ x = torch_npu.npu_fused_infer_attention_score_v2(
532+ q,
533+ k,
534+ v,
535+ input_layout="TND",
536+ num_query_heads=n,
537+ num_key_value_heads=n,
538+ softmax_scale=1.0 / math.sqrt(d),
539+ dequant_scale_query=q_scale,
540+ dequant_scale_key=k_scale,
541+ dequant_scale_value=v_scale,
542+ actual_seq_qlen=actual_seq_qlen,
543+ actual_seq_kvlen=actual_seq_kvlen,
544+ sparse_mode=0, # could be 0/3, atten_mask is needed if set 3
545+ query_quant_mode=6,
546+ key_quant_mode=6,
547+ value_quant_mode=8,
548+ query_dtype=torch.float8_e4m3fn,
549+ key_dtype=torch.float8_e4m3fn,
550+ value_dtype=torch.float8_e4m3fn,
551+ dequant_scale_query_dtype=torch_npu.float8_e8m0fnu,
552+ dequant_scale_key_dtype=torch_npu.float8_e8m0fnu,
553+ dequant_scale_value_dtype=torch_npu.float8_e8m0fnu,
554+ out_dtype=query.dtype,
555+ )[0]
556+ 
557+ if layout == "BNSD":
558+ # [B*S, N, D] -> [B, S, N, D] -> [B, N, S, D]
559+ x = x.reshape(b, s, n, d).permute(0, 2, 1, 3)
560+ elif layout == "BSND":
561+ # [B*S, N, D] -> [B, S, N, D]
562+ x = x.reshape(b, s, n, d)
563+ 
564+ return x
565+ 
566+ 
497class MXFP4QuantFA(nn.Module):567class MXFP4QuantFA(nn.Module):
498 def __init__(self, prefix=None, weights=None, **kwargs):568 def __init__(self, prefix=None, weights=None, **kwargs):
499 super().__init__()569 super().__init__()
Mmindiesd/quantization/mode.py+2-0
@@ -43,6 +43,7 @@ class QuantAlgorithm(StrEnum):
43 INT8 = "INT8"43 INT8 = "INT8"
44 MIXED_PERCISION = "MIXED_PERCISION"44 MIXED_PERCISION = "MIXED_PERCISION"
45 FP8_DYNAMIC = "FP8_DYNAMIC"45 FP8_DYNAMIC = "FP8_DYNAMIC"
46+ MXFP8_DYNAMIC = "MXFP8_DYNAMIC"
46 MXFP4_DYNAMIC = "MXFP4_DYNAMIC"47 MXFP4_DYNAMIC = "MXFP4_DYNAMIC"
47 NO_QUANT = "NO_QUANT"48 NO_QUANT = "NO_QUANT"
48 W4A4_MXFP4_SVD = "W4A4_MXFP4_SVD"49 W4A4_MXFP4_SVD = "W4A4_MXFP4_SVD"
@@ -185,6 +186,7 @@ class QuantMode:
185 QuantAlgorithm.W8A8_PER_CHANNEL_PER_TOKEN: QuantMode.use_smooth_quant(per_token=True, per_channel=True),186 QuantAlgorithm.W8A8_PER_CHANNEL_PER_TOKEN: QuantMode.use_smooth_quant(per_token=True, per_channel=True),
186 QuantAlgorithm.W8A8_PER_TENSOR_PER_TOKEN: QuantMode.use_smooth_quant(per_token=True, per_channel=False),187 QuantAlgorithm.W8A8_PER_TENSOR_PER_TOKEN: QuantMode.use_smooth_quant(per_token=True, per_channel=False),
187 QuantAlgorithm.FP8_DYNAMIC: QuantMode.from_descriptor(QuantModeDescriptor(use_fa_quant=True)),188 QuantAlgorithm.FP8_DYNAMIC: QuantMode.from_descriptor(QuantModeDescriptor(use_fa_quant=True)),
189+ QuantAlgorithm.MXFP8_DYNAMIC: QuantMode.from_descriptor(QuantModeDescriptor(use_fa_quant=True)),
188 QuantAlgorithm.MXFP4_DYNAMIC: QuantMode.from_descriptor(QuantModeDescriptor(use_fa_quant=True)),190 QuantAlgorithm.MXFP4_DYNAMIC: QuantMode.from_descriptor(QuantModeDescriptor(use_fa_quant=True)),
189 QuantAlgorithm.W8A8_MXFP8: QuantMode.use_smooth_quant(per_token=False, per_channel=False),191 QuantAlgorithm.W8A8_MXFP8: QuantMode.use_smooth_quant(per_token=False, per_channel=False),
190 QuantAlgorithm.W4A4_MXFP4_SVD: QuantMode.use_smooth_quant(per_token=False, per_channel=False),192 QuantAlgorithm.W4A4_MXFP4_SVD: QuantMode.use_smooth_quant(per_token=False, per_channel=False),
Mmindiesd/quantization/quantize.py+3-0
@@ -29,6 +29,7 @@ from .layer import (
29 W8A8TimeStepQuantLinear,29 W8A8TimeStepQuantLinear,
30 WeightQuantLinear,30 WeightQuantLinear,
31 FP8RotateQuantFA,31 FP8RotateQuantFA,
32+ MXFP8RotateQuantFA,
32 MXFP4QuantFA,33 MXFP4QuantFA,
33 W8A8MXFP8QuantLinear,34 W8A8MXFP8QuantLinear,
34 W4A4MXFP4QuantLinear,35 W4A4MXFP4QuantLinear,
@@ -160,6 +161,8 @@ def add_fa_quant(layer, cfg, prefix, quant_weights, **kwargs):
160 layer.fa_quant = MXFP4QuantFA(prefix, quant_weights, **kwargs)161 layer.fa_quant = MXFP4QuantFA(prefix, quant_weights, **kwargs)
161 elif cfg.quant_algo in [QuantAlgorithm.FP8_DYNAMIC]:162 elif cfg.quant_algo in [QuantAlgorithm.FP8_DYNAMIC]:
162 layer.fa_quant = FP8RotateQuantFA(prefix, quant_weights)163 layer.fa_quant = FP8RotateQuantFA(prefix, quant_weights)
164+ elif cfg.quant_algo in [QuantAlgorithm.MXFP8_DYNAMIC]:
165+ layer.fa_quant = MXFP8RotateQuantFA(prefix, quant_weights)
163 166 
164 167 
165def normalize_quant_config(kwargs):168def normalize_quant_config(kwargs):
Mtests/quantization/test_quantize.py+12-0
@@ -22,6 +22,7 @@ from mindiesd.quantization.layer import (
22 W8A8QuantBaseLinear,22 W8A8QuantBaseLinear,
23 WeightQuantLinear,23 WeightQuantLinear,
24 FP8RotateQuantFA,24 FP8RotateQuantFA,
25+ MXFP8RotateQuantFA,
25 W8A8MXFP8QuantLinear,26 W8A8MXFP8QuantLinear,
26 W4A4QuantLinear,27 W4A4QuantLinear,
27 W4A4MXFP4QuantLinear,28 W4A4MXFP4QuantLinear,
@@ -518,6 +519,17 @@ class TestAddFAQuant(unittest.TestCase):
518 add_fa_quant(layer, cfg, "test_layer", self.weights)519 add_fa_quant(layer, cfg, "test_layer", self.weights)
519 self.assertFalse(hasattr(layer, 'fa_quant'))520 self.assertFalse(hasattr(layer, 'fa_quant'))
520 521 
522+ def test_add_fa_quant_with_mxfp8_layer(self):
523+ # 创建一个具有必要属性的模拟层
524+ class MockLayer(nn.Module):
525+ pass
526+ 
527+ layer = MockLayer()
528+ cfg = QuantConfig(quant_algo=QuantAlgorithm.MXFP8_DYNAMIC)
529+ add_fa_quant(layer, cfg, "test_layer", create_mock_handler(self.weights))
530+ self.assertTrue(hasattr(layer, 'fa_quant'))
531+ self.assertIsInstance(layer.fa_quant, MXFP8RotateQuantFA)
532+ 
521 533 
522@unittest.skipIf(534@unittest.skipIf(
523 os.environ.get("MINDIE_TEST_MODE", "ALL") == "CPU", "Skip NPU-dependent tests when MINDIE_TEST_MODE is CPU."535 os.environ.get("MINDIE_TEST_MODE", "ALL") == "CPU", "Skip NPU-dependent tests when MINDIE_TEST_MODE is CPU."