@@ -702,7 +702,7 @@ def get_dense_moe_layers(config):
702702 num_moe_layers = config .num_decoder_layers // config .interleave_moe_layer_step
703703 num_dense_layers = config .num_decoder_layers - num_moe_layers
704704 return num_dense_layers , num_moe_layers
705- elif config .decoder_block in (DecoderBlockType .QWEN3_NEXT , DecoderBlockType .QWEN3_5 ):
705+ elif config .decoder_block in (DecoderBlockType .QWEN3_NEXT , DecoderBlockType .QWEN3_5 , DecoderBlockType . DEEPSEEK4 ):
706706 return 0 , config .num_decoder_layers
707707 elif config .decoder_block == DecoderBlockType .DEFAULT :
708708 raise ValueError ("Unsupported decoder block for dense/MoE layer calculation" )
@@ -938,6 +938,163 @@ def calculate_vision_encoder_tflops(config):
938938 return mm_total_tflops , mm_learnable_weight_tflops , mm_attention_tflops
939939
940940
941+ def calculate_mhc_flops_per_layer (config ):
942+ """Calculates the training FLOPs per layer for Manifold-Constrained Hyper-Connections (mHC)."""
943+ batch_size = config .per_device_batch_size
944+ seq_len = config .max_target_length
945+ k_mhc = getattr (config , "mhc_expansion_rate" , 1 )
946+ emb_dim = config .emb_dim
947+
948+ # Inside mhc.py, inputs are projected to an expanded manifold space bounded by k_mhc.
949+ # pre_alpha / post_alpha maps input [B, S, k * D] to [B, S, k]
950+ pre_alpha_flops = 2 * batch_size * seq_len * (k_mhc * emb_dim ) * k_mhc
951+ pre_contract_flops = 2 * batch_size * seq_len * k_mhc * emb_dim
952+ post_alpha_flops = 2 * batch_size * seq_len * (k_mhc * emb_dim ) * k_mhc
953+ post_outer_flops = batch_size * seq_len * k_mhc * emb_dim
954+ # res_alpha maps input [B, S, k * D] to [B, S, k^2]
955+ res_alpha_flops = 2 * batch_size * seq_len * (k_mhc * emb_dim ) * (k_mhc ** 2 )
956+ res_contract_flops = 2 * batch_size * seq_len * (k_mhc ** 2 ) * emb_dim
957+
958+ return pre_alpha_flops + pre_contract_flops + post_alpha_flops + post_outer_flops + res_alpha_flops + res_contract_flops
959+
960+
961+ def calculate_deepseek4_tflops_training_per_device (config , total_ffn_flops_all_layers , embedding_flops ):
962+ """Calculates the training TFLOPs per device for DeepSeek-V4.
963+
964+ DeepSeek-V4 adopts a multi-track attention topology and Manifold-Constrained
965+ Hyper-Connections (mHC). This function models:
966+ 1. Base Attention block projections (Q/KV/Grouped Out mapping).
967+ 2. mHC projections (pre, post, residual alpha/contraction projections) applied twice per layer.
968+ 3. Prefix-only Sliding Window track (C=0).
969+ 4. HCA (Hybrid Compressed Attention, C=128) incorporating global context.
970+ 5. CSA (Causal Sparse Attention, C=4) containing poolers, indexer top-k query scoring,
971+ and sparse sequence attention.
972+
973+ Args:
974+ config: MaxTextConfig instance containing architectural hyperparameters.
975+ total_ffn_flops_all_layers: Pre-computed Feed-Forward (MoE/Shared MLP) FLOPs.
976+ embedding_flops: Pre-computed input token embedding layer FLOPs.
977+
978+ Returns:
979+ A tuple of (total_attn_flops_in_tflops, total_weight_flops_in_tflops).
980+ """
981+ batch_size = config .per_device_batch_size
982+ seq_len = config .max_target_length
983+ sliding_window_size = config .sliding_window_size
984+ num_layers = config .num_decoder_layers
985+
986+ # DeepSeek-V4 Attention topology compression rates (HCA = 128, CSA = 4)
987+ HCA_RATIO = 128
988+ CSA_RATIO = 4
989+
990+ # 1. Dynamic YAML-compliant Layer Counting
991+ # DeepSeek-V4 alternates layers with different compression rates (C = 0, 128, 4).
992+ # If the YAML config doesn't declare it explicitly, we default to the standard
993+ # DeepSeek-V4 profile (2 prefix sliding window, 20 HCA, and 21 CSA layers).
994+ c_ratios = getattr (config , "compress_ratios" , [])
995+ if not c_ratios :
996+ num_sliding_layers , num_hca_layers , num_csa_layers = 2 , 20 , 21
997+ else :
998+ num_sliding_layers = c_ratios .count (0 )
999+ num_hca_layers = c_ratios .count (HCA_RATIO )
1000+ num_csa_layers = c_ratios .count (CSA_RATIO )
1001+
1002+ # 2. Base Attention Block Projections (Executed on all layers)
1003+ # - Q projection: projects inputs to q_lora_rank, then up-projects to query_heads * head_dim.
1004+ # FLOPs = 2 * B * S * (D * Q_rank + Q_rank * H * D_h)
1005+ wq_flops = (
1006+ 2
1007+ * batch_size
1008+ * seq_len
1009+ * ((config .emb_dim * config .q_lora_rank ) + (config .q_lora_rank * config .num_query_heads * config .head_dim ))
1010+ )
1011+ # - KV projection: embeds to kv_heads * head_dim.
1012+ # FLOPs = 2 * B * S * D * (KV_heads * D_h)
1013+ wkv_flops = 2 * batch_size * seq_len * config .emb_dim * (config .num_kv_heads * config .head_dim )
1014+ # - Grouped Out projection: projects out groups of heads down to o_lora_rank, then up to emb_dim.
1015+ # FLOPs = 2 * B * S * (H * D_h) * o_lora_rank + 2 * B * S * (o_groups * o_lora_rank) * D
1016+ grp_feat = (config .num_query_heads * config .head_dim ) // config .o_groups
1017+ o_flops = (2 * batch_size * seq_len * config .o_groups * grp_feat * config .o_lora_rank ) + (
1018+ 2 * batch_size * seq_len * (config .o_groups * config .o_lora_rank ) * config .emb_dim
1019+ )
1020+ base_attention_weights = wq_flops + wkv_flops + o_flops
1021+
1022+ # 3. Manifold-Constrained Hyper-Connections (mHC) Overheads
1023+ # Inside mhc.py, inputs are projected to an expanded manifold space.
1024+ # These operations are executed twice per layer (wrapping the attention block and FFN block).
1025+ if getattr (config , "mhc_expansion_rate" , 1 ) > 1 :
1026+ mhc_flops_per_layer = calculate_mhc_flops_per_layer (config )
1027+ else :
1028+ mhc_flops_per_layer = 0
1029+
1030+ # 4. Multi-Track Sequence Length Operations
1031+ # - Track 1: Sliding Window Prefix (C=0)
1032+ # Uses exact causal sliding window surface area formula to count attention dot products:
1033+ # FLOPs = 4 * B * (S * W - 0.5 * W^2) * H * D_h
1034+ prefix_attn = (
1035+ 4
1036+ * batch_size
1037+ * (seq_len * sliding_window_size - 0.5 * sliding_window_size ** 2 )
1038+ * config .num_query_heads
1039+ * config .head_dim
1040+ )
1041+
1042+ # - Track 2: HCA Compressed Branch (C=128)
1043+ # Includes HCA compressor dense projection: D -> D_h
1044+ hca_pooler = 4 * batch_size * seq_len * config .emb_dim * config .head_dim
1045+ # Calculates prefix sliding window attention + global cross-attention to the S/128 elements
1046+ hca_attn = prefix_attn + 2 * batch_size * seq_len * (seq_len / HCA_RATIO ) * config .num_query_heads * config .head_dim
1047+
1048+ # - Track 3: CSA Sparse Branch (C=4)
1049+ # - Indexer Projections: Q_rank -> H_idx * D_idx, and D -> H_idx
1050+ idx_proj = (
1051+ 2
1052+ * batch_size
1053+ * seq_len
1054+ * (
1055+ (config .q_lora_rank * config .indexer_n_heads * config .indexer_head_dim )
1056+ + (config .emb_dim * config .indexer_n_heads )
1057+ )
1058+ )
1059+ # - Indexer query-block scoring (divided by 2 for causal mask)
1060+ idx_score = batch_size * seq_len * (seq_len / CSA_RATIO ) * config .indexer_n_heads * config .indexer_head_dim
1061+ # - Indexer head reduction (divided by 2 for causal mask)
1062+ idx_reduce = batch_size * seq_len * (seq_len / CSA_RATIO ) * config .indexer_n_heads
1063+ # - Indexer key/gate pooler projections: Down-projects D -> 2 * D_idx
1064+ idx_kv_gate_pool = 4 * batch_size * seq_len * config .emb_dim * (2 * config .indexer_head_dim )
1065+ csa_indexer = idx_proj + idx_score + idx_reduce + idx_kv_gate_pool
1066+
1067+ # Includes CSA compressor pooler projection: D -> 2 * D_h
1068+ csa_pooler = 4 * batch_size * seq_len * config .emb_dim * (2 * config .head_dim )
1069+ # - Sparse causal attention: attends to top-k blocks of size 4 in preceding sequence of size S/4
1070+ csa_k = min (config .indexer_topk , seq_len // CSA_RATIO )
1071+ # We reuse the mask multiplier helper for the csa causal ratio:
1072+ # Ratio = (K/T) - 0.5 * (K/T)^2
1073+ csa_mask_ratio = calculate_indexer_mask_ratio (csa_k , seq_len // CSA_RATIO )
1074+ csa_sparse_attn = (
1075+ 4 * batch_size * (seq_len * (seq_len / CSA_RATIO )) * config .num_query_heads * config .head_dim * csa_mask_ratio
1076+ )
1077+ csa_attn = prefix_attn + csa_sparse_attn + csa_indexer
1078+
1079+ # 5. Pipeline TFLOP Aggregation
1080+ total_attn_flops = (num_sliding_layers * prefix_attn ) + (num_hca_layers * hca_attn ) + (num_csa_layers * csa_attn )
1081+
1082+ total_weight_flops = (
1083+ (num_layers * base_attention_weights )
1084+ + (2 * num_layers * mhc_flops_per_layer ) # mHC runs twice per layer
1085+ + (num_hca_layers * hca_pooler )
1086+ + (num_csa_layers * csa_pooler )
1087+ + total_ffn_flops_all_layers
1088+ + embedding_flops
1089+ )
1090+
1091+ # Scale final values by 3x to account for Forward + Backward (2x Forward) training passes
1092+ return (
1093+ total_attn_flops * 3 / 10 ** 12 ,
1094+ total_weight_flops * 3 / 10 ** 12 ,
1095+ )
1096+
1097+
9411098def calculate_tflops_training_per_device (config , log = True ):
9421099 """Calculate training TFLOP"""
9431100 # MLP flops
@@ -950,6 +1107,7 @@ def calculate_tflops_training_per_device(config, log=True):
9501107 DecoderBlockType .QWEN3_NEXT ,
9511108 DecoderBlockType .QWEN3_5 ,
9521109 DecoderBlockType .GEMMA4 ,
1110+ DecoderBlockType .DEEPSEEK4 ,
9531111 ):
9541112 total_ffn_flops = calculate_routed_and_shared_ffn_tflops_per_device (config )
9551113 is_ffn_flops_already_total = True
@@ -1040,6 +1198,10 @@ def calculate_tflops_training_per_device(config, log=True):
10401198 # KV sharing, double-wide MLP on shared layers, and the per-layer-embedding
10411199 # block.
10421200 attention_tflops , learnable_weight_tflops = calculate_gemma4_small_tflops_training_per_device (config , embedding_flops )
1201+ elif config .decoder_block == DecoderBlockType .DEEPSEEK4 :
1202+ attention_tflops , learnable_weight_tflops = calculate_deepseek4_tflops_training_per_device (
1203+ config , total_ffn_flops_all_layers , embedding_flops
1204+ )
10431205 elif config .decoder_block == DecoderBlockType .DEEPSEEK :
10441206 learnable_weight_tflops = (
10451207 (total_ffn_flops_all_layers + (qkv_flops + projection_flops ) * config .num_decoder_layers + embedding_flops )
0 commit comments