@@ -32,6 +32,7 @@ def is_cuda_available():
3232
3333
3434CUDA_AVAILABLE = is_cuda_available ()
35+ DEFAULT_BLOCK_SHAPE = [128 , 128 ]
3536
3637
3738def to_int8 (tensor : torch .Tensor ):
@@ -455,6 +456,117 @@ def _fp8_input_fn(self, config, dtype):
455456 )
456457
457458
459+ class FusedMoEFP8BlockwiseBenchmark (Benchmark ):
460+ """
461+ Benchmark for fused_experts_impl with FP8 W8A8 block-wise quantization.
462+
463+ Weights are stored in FP8 E4M3 and accompanied by block scales.
464+ Activations are dynamically quantized per-token per-group inside the kernel.
465+ """
466+
467+ def __init__ (self , op_name , torch_op , dtypes ):
468+ super ().__init__ (op_name = op_name , torch_op = torch_op , dtypes = dtypes )
469+ self .block_shape = DEFAULT_BLOCK_SHAPE
470+
471+ def set_shapes (self , shape_file_path = None ):
472+ # (num_tokens, num_experts, hidden_size, intermediate_size, topk)
473+ self .shapes = [
474+ # Mixtral-like shapes
475+ (1 , 8 , 4096 , 14336 , 2 ),
476+ (4 , 8 , 4096 , 14336 , 2 ),
477+ (16 , 8 , 4096 , 14336 , 2 ),
478+ (64 , 8 , 4096 , 14336 , 2 ),
479+ (128 , 8 , 4096 , 14336 , 2 ),
480+ (256 , 8 , 4096 , 14336 , 2 ),
481+ (512 , 8 , 4096 , 14336 , 2 ),
482+ # DeepSeek-V3-like shapes (TP=8 shard)
483+ (1 , 256 , 7168 , 2048 , 8 ),
484+ (4 , 256 , 7168 , 2048 , 8 ),
485+ (16 , 256 , 7168 , 2048 , 8 ),
486+ (64 , 256 , 7168 , 2048 , 8 ),
487+ (128 , 256 , 7168 , 2048 , 8 ),
488+ (256 , 256 , 7168 , 2048 , 8 ),
489+ # Qwen3.5-397B-A17B
490+ (1 , 512 , 4096 , 1024 , 10 ),
491+ (4 , 512 , 4096 , 1024 , 10 ),
492+ (16 , 512 , 4096 , 1024 , 10 ),
493+ (64 , 512 , 4096 , 1024 , 10 ),
494+ (128 , 512 , 4096 , 1024 , 10 ),
495+ (256 , 512 , 4096 , 1024 , 10 ),
496+ ]
497+
498+ def get_input_iter (self , cur_dtype ):
499+ del cur_dtype
500+ for config in self .shapes :
501+ yield from self ._fp8_blockwise_input_fn (config )
502+
503+ def _fp8_blockwise_input_fn (self , config ):
504+ num_tokens , num_experts , hidden_size , intermediate_size , topk = config
505+ block_n , block_k = self .block_shape
506+ device = flag_gems .device
507+ dtype = torch .bfloat16
508+
509+ hidden_states = torch .randn (num_tokens , hidden_size , device = device , dtype = dtype )
510+ w1_fp8 = (
511+ torch .randn (
512+ num_experts ,
513+ intermediate_size * 2 ,
514+ hidden_size ,
515+ device = device ,
516+ dtype = torch .bfloat16 ,
517+ )
518+ * (1.0 / hidden_size ** 0.5 )
519+ ).to (torch .float8_e4m3fn )
520+ w2_fp8 = (
521+ torch .randn (
522+ num_experts ,
523+ hidden_size ,
524+ intermediate_size ,
525+ device = device ,
526+ dtype = torch .bfloat16 ,
527+ )
528+ * (1.0 / intermediate_size ** 0.5 )
529+ ).to (torch .float8_e4m3fn )
530+
531+ w1_scale = (
532+ torch .rand (
533+ num_experts ,
534+ ceil (intermediate_size * 2 / block_n ),
535+ ceil (hidden_size / block_k ),
536+ device = device ,
537+ dtype = torch .float32 ,
538+ )
539+ + 0.01
540+ )
541+ w2_scale = (
542+ torch .rand (
543+ num_experts ,
544+ ceil (hidden_size / block_n ),
545+ ceil (intermediate_size / block_k ),
546+ device = device ,
547+ dtype = torch .float32 ,
548+ )
549+ + 0.01
550+ )
551+
552+ gating = torch .randn (
553+ num_tokens , num_experts , device = device , dtype = torch .float32
554+ )
555+ topk_weights , topk_ids = torch .topk (torch .softmax (gating , dim = - 1 ), topk , dim = - 1 )
556+ topk_weights = topk_weights / topk_weights .sum (dim = - 1 , keepdim = True )
557+ topk_weights = topk_weights .to (torch .float32 )
558+
559+ yield (
560+ hidden_states ,
561+ w1_fp8 ,
562+ w2_fp8 ,
563+ w1_scale ,
564+ w2_scale ,
565+ topk_weights ,
566+ topk_ids ,
567+ )
568+
569+
458570def _vllm_fused_moe_fp8_wrapper (
459571 hidden_states , w1 , w2 , topk_weights , topk_ids , w1_scale , w2_scale
460572):
@@ -489,6 +601,42 @@ def _gems_fused_moe_fp8_wrapper(
489601 )
490602
491603
604+ def _vllm_fused_moe_fp8_blockwise_wrapper (
605+ hidden_states , w1 , w2 , w1_scale , w2_scale , topk_weights , topk_ids
606+ ):
607+ """Wrapper to call vllm fused_experts_impl with block-wise FP8."""
608+ return vllm_fused_experts_impl (
609+ hidden_states .clone (),
610+ w1 ,
611+ w2 ,
612+ topk_weights ,
613+ topk_ids ,
614+ inplace = False ,
615+ activation = "silu" ,
616+ use_fp8_w8a8 = True ,
617+ w1_scale = w1_scale ,
618+ w2_scale = w2_scale ,
619+ block_shape = DEFAULT_BLOCK_SHAPE ,
620+ )
621+
622+
623+ def _gems_fused_moe_fp8_blockwise_wrapper (
624+ hidden_states , w1 , w2 , w1_scale , w2_scale , topk_weights , topk_ids
625+ ):
626+ """Wrapper to call FlagGems fused_experts_impl with block-wise FP8."""
627+ return flag_gems .fused_experts_impl (
628+ hidden_states ,
629+ w1 ,
630+ w2 ,
631+ topk_weights ,
632+ topk_ids ,
633+ use_fp8_w8a8 = True ,
634+ w1_scale = w1_scale ,
635+ w2_scale = w2_scale ,
636+ block_shape = DEFAULT_BLOCK_SHAPE ,
637+ )
638+
639+
492640@pytest .mark .fused_moe
493641@pytest .mark .skipif (
494642 not (HAS_VLLM_FUSED_MOE and CUDA_AVAILABLE ),
@@ -507,6 +655,24 @@ def test_perf_fused_moe_fp8_gems_vs_vllm():
507655 bench .run ()
508656
509657
658+ @pytest .mark .fused_moe
659+ @pytest .mark .skipif (
660+ not (HAS_VLLM_FUSED_MOE and CUDA_AVAILABLE ),
661+ reason = "requires vLLM and NVIDIA Hopper architecture for FP8 blockwise" ,
662+ )
663+ def test_perf_fused_moe_fp8_blockwise_gems_vs_vllm ():
664+ """
665+ Benchmark FlagGems vs vLLM fused_experts_impl with FP8 W8A8 block-wise quantization.
666+ """
667+ bench = FusedMoEFP8BlockwiseBenchmark (
668+ op_name = "fused_moe_fp8_blockwise_gems_vs_vllm" ,
669+ torch_op = _vllm_fused_moe_fp8_blockwise_wrapper ,
670+ dtypes = [torch .bfloat16 ],
671+ )
672+ bench .set_gems (_gems_fused_moe_fp8_blockwise_wrapper )
673+ bench .run ()
674+
675+
510676class FusedMoEINT8Benchmark (Benchmark ):
511677 """
512678 Benchmark for fused_experts_impl with INT8 W8A8 quantization.
0 commit comments