@@ -61,6 +61,14 @@ class MockModelConfig:
6161 group_query_attention : bool = True
6262 gated_linear_unit : bool = True
6363 activation_func : object = field (default = None )
64+ # GDN (Gated DeltaNet) settings
65+ experimental_attention_variant : str | None = None
66+ linear_attention_freq : int | list | None = None
67+ linear_conv_kernel_dim : int = 4
68+ linear_key_head_dim : int = 128
69+ linear_value_head_dim : int = 128
70+ linear_num_key_heads : int = 16
71+ linear_num_value_heads : int = 48
6472
6573 def __post_init__ (self ):
6674 import torch .nn .functional as F
@@ -380,6 +388,204 @@ def test_swiglu_scaling_factor(self):
380388 assert flops_swiglu == expected_swiglu , f"SwiGLU: expected { expected_swiglu :.2e} but got { flops_swiglu :.2e} "
381389
382390
391+ class TestGDNLayerFlops :
392+ """Tests for Gated DeltaNet (GDN) FLOPs calculation in transformer_flops path."""
393+
394+ def _qwen35_27b_config (self , ** overrides ):
395+ """Return a MockModelConfig resembling Qwen3.5-27B (dense, 64 layers, freq=4)."""
396+ defaults = dict (
397+ num_layers = 64 ,
398+ hidden_size = 5120 ,
399+ seq_length = 4096 ,
400+ ffn_hidden_size = 17408 ,
401+ num_attention_heads = 24 ,
402+ num_query_groups = 4 ,
403+ kv_channels = 256 ,
404+ vocab_size = 248320 ,
405+ make_vocab_size_divisible_by = 128 ,
406+ tensor_model_parallel_size = 1 ,
407+ gated_linear_unit = True ,
408+ experimental_attention_variant = "gated_delta_net" ,
409+ linear_attention_freq = 4 ,
410+ linear_conv_kernel_dim = 4 ,
411+ linear_key_head_dim = 128 ,
412+ linear_value_head_dim = 128 ,
413+ linear_num_key_heads = 16 ,
414+ linear_num_value_heads = 48 ,
415+ )
416+ defaults .update (overrides )
417+ return MockModelConfig (** defaults )
418+
419+ def test_gdn_flops_differ_from_pure_attention (self ):
420+ """GDN-enabled config should produce different FLOPs than pure-attention baseline."""
421+ batch_size = 1
422+ gdn_cfg = MockConfigContainer (model = self ._qwen35_27b_config ())
423+ baseline_cfg = MockConfigContainer (model = self ._qwen35_27b_config (experimental_attention_variant = None ))
424+ gdn_flops = num_floating_point_operations (gdn_cfg , batch_size = batch_size )
425+ baseline_flops = num_floating_point_operations (baseline_cfg , batch_size = batch_size )
426+ assert gdn_flops != baseline_flops , "GDN FLOPs should differ from pure-attention FLOPs"
427+ assert gdn_flops > 0
428+
429+ def test_gdn_only_layers (self ):
430+ """With linear_attention_freq=1 (no standard attn), self_attn_term should be pure GDN."""
431+ batch_size = 1
432+ num_layers = 4
433+ hidden_size = 1024
434+ seq_length = 512
435+ vocab_size = 32000
436+ qk_head_dim = 64
437+ v_head_dim = 64
438+ num_qk_heads = 8
439+ num_v_heads = 16
440+ conv_kernel_dim = 4
441+
442+ model_cfg = MockModelConfig (
443+ num_layers = num_layers ,
444+ hidden_size = hidden_size ,
445+ seq_length = seq_length ,
446+ ffn_hidden_size = 4096 ,
447+ num_attention_heads = 8 ,
448+ num_query_groups = 8 ,
449+ kv_channels = 128 ,
450+ vocab_size = vocab_size ,
451+ make_vocab_size_divisible_by = 128 ,
452+ tensor_model_parallel_size = 1 ,
453+ gated_linear_unit = False ,
454+ experimental_attention_variant = "gated_delta_net" ,
455+ linear_attention_freq = 1 ,
456+ linear_conv_kernel_dim = conv_kernel_dim ,
457+ linear_key_head_dim = qk_head_dim ,
458+ linear_value_head_dim = v_head_dim ,
459+ linear_num_key_heads = num_qk_heads ,
460+ linear_num_value_heads = num_v_heads ,
461+ )
462+ cfg = MockConfigContainer (model = model_cfg )
463+ actual_flops = num_floating_point_operations (cfg , batch_size = batch_size )
464+
465+ # freq=1: pattern = [0 if (i+1)%1==0 else 1 for i in range(4)] = [0,0,0,0]
466+ # All layers are standard attention, 0 GDN layers.
467+ # This is because freq=1 means every layer is standard attention.
468+ # So actual_flops should equal baseline (no GDN).
469+ baseline_cfg = MockConfigContainer (
470+ model = MockModelConfig (
471+ num_layers = num_layers ,
472+ hidden_size = hidden_size ,
473+ seq_length = seq_length ,
474+ ffn_hidden_size = 4096 ,
475+ num_attention_heads = 8 ,
476+ num_query_groups = 8 ,
477+ kv_channels = 128 ,
478+ vocab_size = vocab_size ,
479+ make_vocab_size_divisible_by = 128 ,
480+ tensor_model_parallel_size = 1 ,
481+ gated_linear_unit = False ,
482+ )
483+ )
484+ baseline_flops = num_floating_point_operations (baseline_cfg , batch_size = batch_size )
485+ assert actual_flops == baseline_flops , (
486+ "freq=1 means every layer is standard attention, so FLOPs should match baseline"
487+ )
488+
489+ def test_gdn_layer_freq_list (self ):
490+ """Test GDN with linear_attention_freq as a list pattern."""
491+ batch_size = 1
492+ # [1, 1, 0, 1, 1, 0, 1, 1] means 6 GDN, 2 standard
493+ freq_list = [1 , 1 , 0 , 1 , 1 , 0 , 1 , 1 ]
494+ model_cfg = self ._qwen35_27b_config (
495+ num_layers = 8 ,
496+ linear_attention_freq = freq_list ,
497+ )
498+ cfg = MockConfigContainer (model = model_cfg )
499+ flops = num_floating_point_operations (cfg , batch_size = batch_size )
500+ assert flops > 0
501+
502+ def test_gdn_exact_self_attn_term (self ):
503+ """Verify the GDN self_attn_term matches the expected formula from Megatron-LM."""
504+ batch_size = 1
505+ num_layers = 4
506+ hidden_size = 1024
507+ seq_length = 256
508+ vocab_size = 32000
509+ qk_head_dim = 64
510+ v_head_dim = 64
511+ num_qk_heads = 8
512+ num_v_heads = 16
513+ conv_kernel_dim = 4
514+ ffn_hidden_size = 4096
515+
516+ qk_dim = qk_head_dim * num_qk_heads
517+ v_dim = v_head_dim * num_v_heads
518+
519+ # freq=2: layers 0,2 are GDN (pattern[i]=1), layers 1,3 are standard (pattern[i]=0)
520+ model_cfg = MockModelConfig (
521+ num_layers = num_layers ,
522+ hidden_size = hidden_size ,
523+ seq_length = seq_length ,
524+ ffn_hidden_size = ffn_hidden_size ,
525+ num_attention_heads = 8 ,
526+ num_query_groups = 8 ,
527+ kv_channels = 128 ,
528+ vocab_size = vocab_size ,
529+ make_vocab_size_divisible_by = 128 ,
530+ tensor_model_parallel_size = 1 ,
531+ gated_linear_unit = False ,
532+ experimental_attention_variant = "gated_delta_net" ,
533+ linear_attention_freq = 2 ,
534+ linear_conv_kernel_dim = conv_kernel_dim ,
535+ linear_key_head_dim = qk_head_dim ,
536+ linear_value_head_dim = v_head_dim ,
537+ linear_num_key_heads = num_qk_heads ,
538+ linear_num_value_heads = num_v_heads ,
539+ )
540+ cfg = MockConfigContainer (model = model_cfg )
541+ gdn_flops = num_floating_point_operations (cfg , batch_size = batch_size )
542+
543+ # Compute expected manually
544+ expansion_factor = 3 * 2 * 2 # 12
545+ # Standard attention per-layer (MHA, num_query_groups==num_attention_heads so ratio=1)
546+ kv_channels = 128
547+ query_projection_size = kv_channels * 8
548+ query_projection_to_hidden_size_ratio = query_projection_size / hidden_size
549+ standard_attn_per_layer = (
550+ expansion_factor
551+ * hidden_size
552+ * hidden_size
553+ * ((1 + 8 / 8 + seq_length / hidden_size / 2 ) * query_projection_to_hidden_size_ratio )
554+ )
555+ # GDN per-layer
556+ gdn_per_layer = (
557+ 3
558+ * 2
559+ * (
560+ hidden_size * (2 * qk_dim + 2 * v_dim + 2 * num_v_heads )
561+ + conv_kernel_dim * (2 * qk_dim + v_dim )
562+ + num_v_heads * (v_head_dim ** 2 ) * 4
563+ + hidden_size * v_dim
564+ )
565+ )
566+ # freq=2: pattern = [1, 0, 1, 0] -> 2 GDN, 2 standard
567+ expected_self_attn = gdn_per_layer * 2 + standard_attn_per_layer * 2
568+ # MLP: gated_linear_unit=False -> gated_linear_multiplier=1
569+ expected_mlp = expansion_factor * num_layers * hidden_size * ffn_hidden_size * 1
570+ # Logit
571+ padded_vocab = vocab_size # 32000 is already divisible by 128
572+ expected_logit = 3 * 2 * hidden_size * padded_vocab * 1
573+ expected_total = batch_size * seq_length * (expected_mlp + expected_self_attn + expected_logit )
574+
575+ assert gdn_flops == expected_total , f"Expected { expected_total :.6e} but got { gdn_flops :.6e} "
576+
577+ def test_gdn_more_gdn_layers_changes_flops (self ):
578+ """Increasing GDN layer ratio (higher freq) should change FLOPs."""
579+ batch_size = 1
580+ # freq=4: 3/4 GDN, 1/4 standard
581+ cfg_freq4 = MockConfigContainer (model = self ._qwen35_27b_config (num_layers = 8 , linear_attention_freq = 4 ))
582+ # freq=8: 7/8 GDN, 1/8 standard
583+ cfg_freq8 = MockConfigContainer (model = self ._qwen35_27b_config (num_layers = 8 , linear_attention_freq = 8 ))
584+ flops_freq4 = num_floating_point_operations (cfg_freq4 , batch_size = batch_size )
585+ flops_freq8 = num_floating_point_operations (cfg_freq8 , batch_size = batch_size )
586+ assert flops_freq4 != flops_freq8 , "Different GDN ratios should produce different FLOPs"
587+
588+
383589class TestHybridMtpPatternParsing :
384590 """Tests for hybrid/MTP pattern parsing in FLOPs accounting."""
385591
0 commit comments