Spaces:
Running on Zero
Running on Zero
| # Copyright 2025 Bytedance Ltd. and/or its affiliates | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| from transformers import PretrainedConfig | |
| from . import logging | |
| from .device import get_device_name | |
| logger = logging.get_logger(__name__) | |
| def get_device_flops(unit="T"): | |
| def unit_convert(number, level): | |
| units = ["B", "K", "M", "G", "T", "P"] | |
| if number <= 0: | |
| return number | |
| ptr = 0 | |
| while ptr < len(units) and units[ptr] != level: | |
| number /= 1000 | |
| ptr += 1 | |
| return number | |
| device_name = get_device_name() | |
| flops = float("inf") # INF flops for unkown gpu type | |
| if "H100" in device_name or "H800" in device_name or "H200" in device_name: | |
| flops = 989e12 | |
| elif "A100" in device_name or "A800" in device_name: | |
| flops = 312e12 | |
| elif "L40" in device_name: | |
| flops = 181.05e12 | |
| elif "L20" in device_name: | |
| flops = 119.5e12 | |
| elif "H20" in device_name: | |
| flops = 148e12 | |
| elif "910B" in device_name or "910_93" in device_name: | |
| flops = 354e12 | |
| elif "B200" in device_name: | |
| flops = 2250e12 | |
| flops_unit = unit_convert(flops, unit) | |
| return flops_unit | |
| class VeomniFlopsCounter: | |
| """ | |
| Used to count mfu during training loop | |
| Example: | |
| flops_counter = VeomniFlopsCounter(config) | |
| flops_achieved, flops_promised = flops_counter.estimate_flops(batch_seqlens, delta_time) | |
| """ | |
| def __init__(self, config: PretrainedConfig): | |
| self.estimate_func = { | |
| "qwen2_vl": self._estimate_qwen2_vl_flops, | |
| # the only difference between Qwen2 and Qwen2.5 for counting flops is the window attention | |
| # used in the ViT for Qwen2.5VL which is considered in the _estimate_qwen2_vl_flops function. | |
| "qwen2_5_vl": self._estimate_qwen2_vl_flops, | |
| # qwen3_vl's vit uses full self attention while qwen2-vl/qwen2.5-vl uses window attention. | |
| "qwen3_vl": self._estimate_qwen3_vl_flops, | |
| "qwen3_vl_moe": self._estimate_qwen3_vl_moe_flops, | |
| "deepseek_v3": self._estimate_deepseek_v3_flops, | |
| "qwen3_moe": self._estimate_qwen3_moe_flops, | |
| "llama": self._estimate_llama_flops, | |
| "qwen2": self._estimate_qwen2_flops, | |
| # qwen3_next | |
| "qwen3_next": self._estimate_qwen3_next_flops, | |
| # qwen3 reused _estimate_qwen2_flops func because the only model structure diff between qwen2 dense and qwen3 dense is that | |
| # qwen3 has additional RMSNorm layers for q and k. | |
| # RMSNorm layers have minimal impact at the MFU and can be ignored. | |
| "qwen3": self._estimate_qwen2_flops, | |
| "seed_oss": self._estimate_seed_flops, | |
| "qwen3_5": self._estimate_qwen3_5_family_flops, | |
| "qwen3_5_moe": self._estimate_qwen3_5_family_flops, | |
| "qwen3_5_moe_text": self._estimate_qwen3_5_family_flops, | |
| } | |
| self.config = config | |
| def _estimate_unknown_flops(self, tokens_sum, batch_seqlens, delta_time, **kwargs): | |
| return 0 | |
| def _compute_lm_head_params(hidden_size, vocab_size): | |
| # nn.Embedding is a table lookup, so only the lm_head matmul contributes FLOPs. | |
| return vocab_size * hidden_size | |
| def _estimate_seed_flops(self, tokens_sum, batch_seqlens, delta_time): | |
| hidden_size = self.config.hidden_size | |
| vocab_size = self.config.vocab_size | |
| num_hidden_layers = self.config.num_hidden_layers | |
| num_key_value_heads = self.config.num_key_value_heads | |
| num_attention_heads = self.config.num_attention_heads | |
| intermediate_size = self.config.intermediate_size | |
| head_dim = hidden_size // num_attention_heads | |
| q_size = num_attention_heads * head_dim | |
| k_size = num_key_value_heads * head_dim | |
| v_size = num_key_value_heads * head_dim | |
| # non-attn per layer parm | |
| # llama use SwiGelu, gate, having up and down linear layer in mlp | |
| mlp_N = hidden_size * intermediate_size * 3 | |
| attn_linear_N = hidden_size * (q_size + k_size + v_size + num_attention_heads * head_dim) | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # non-attn all_layer parm | |
| dense_N = (mlp_N + attn_linear_N) * num_hidden_layers + lm_head_N | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * dense_N * tokens_sum | |
| # attn all_layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_attention_heads * num_hidden_layers | |
| # all_layer & all_token fwd & bwd flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def _estimate_deepseek_v3_flops(self, tokens_sum, batch_seqlens, delta_time): | |
| hidden_size = self.config.hidden_size | |
| vocab_size = self.config.vocab_size | |
| moe_intermediate_size = self.config.moe_intermediate_size | |
| num_hidden_layers = self.config.num_hidden_layers | |
| first_k_dense_replace = self.config.first_k_dense_replace | |
| num_query_heads = self.config.num_attention_heads | |
| moe_num_expert = self.config.n_routed_experts | |
| moe_topk = self.config.num_experts_per_tok | |
| share_expert_num = self.config.n_shared_experts | |
| # non-attn per layer parm | |
| moe_gata_N = hidden_size * moe_num_expert | |
| # moe has fc1_1, fc1_2 and fc2 using SwiGLU in ExpertMlp layer & shared experts | |
| moe_expertmlp_N = hidden_size * moe_intermediate_size * (moe_topk + share_expert_num) * 3 | |
| # MLA attn | |
| attn_linear_N = 0 | |
| q_head_dim = self.config.qk_nope_head_dim + self.config.qk_rope_head_dim | |
| if self.config.q_lora_rank is None: | |
| attn_linear_N += hidden_size * num_query_heads * q_head_dim | |
| else: | |
| attn_linear_N += hidden_size * self.config.q_lora_rank | |
| attn_linear_N += num_query_heads * q_head_dim * self.config.q_lora_rank | |
| attn_linear_N += hidden_size * (self.config.kv_lora_rank + self.config.qk_rope_head_dim) | |
| attn_linear_N += ( | |
| num_query_heads | |
| * (q_head_dim - self.config.qk_rope_head_dim + self.config.v_head_dim) | |
| * self.config.kv_lora_rank | |
| ) | |
| attn_linear_N += num_query_heads * self.config.v_head_dim * hidden_size | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # non-attn all_layer parm | |
| moe_N = ( | |
| (moe_gata_N + moe_expertmlp_N + attn_linear_N) * (num_hidden_layers - first_k_dense_replace) | |
| + (hidden_size * self.config.intermediate_size * 3 + attn_linear_N) * first_k_dense_replace | |
| + lm_head_N | |
| ) | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * moe_N * tokens_sum | |
| # attn all_layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen * num_hidden_layers | |
| attn_qkv_flops = 12 * seqlen_square_sum * q_head_dim * num_query_heads | |
| # all_layer & all_token fwd & bwk flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def _estimate_qwen3_moe_flops(self, tokens_sum, batch_seqlens, delta_time): | |
| hidden_size = self.config.hidden_size | |
| vocab_size = self.config.vocab_size | |
| moe_intermediate_size = self.config.moe_intermediate_size | |
| num_hidden_layers = self.config.num_hidden_layers | |
| num_key_value_heads = self.config.num_key_value_heads | |
| num_attention_heads = self.config.num_attention_heads | |
| moe_intermediate_size = self.config.moe_intermediate_size | |
| moe_num_expert = self.config.num_experts | |
| moe_topk = self.config.num_experts_per_tok | |
| head_dim = getattr(self.config, "head_dim", self.config.hidden_size // self.config.num_attention_heads) | |
| q_size = num_attention_heads * head_dim | |
| k_size = num_key_value_heads * head_dim | |
| v_size = num_key_value_heads * head_dim | |
| # non-attn per layer parm | |
| moe_gata_N = hidden_size * moe_num_expert | |
| # moe has gate_proj, up_proj and down_proj using SwiGLU in ExpertMlp layer & shared experts | |
| moe_expertmlp_N = hidden_size * moe_intermediate_size * (moe_topk) * 3 | |
| attn_linear_N = hidden_size * (q_size + k_size + v_size + num_attention_heads * head_dim) | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # non-attn all_layer parm | |
| moe_N = (moe_gata_N + moe_expertmlp_N + attn_linear_N) * (num_hidden_layers) + lm_head_N | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * moe_N * tokens_sum | |
| # attn all_layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_attention_heads * num_hidden_layers | |
| # all_layer & all_token fwd & bwk flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def _estimate_qwen2_flops(self, tokens_sum, batch_seqlens, delta_time): | |
| hidden_size = self.config.hidden_size | |
| vocab_size = self.config.vocab_size | |
| num_hidden_layers = self.config.num_hidden_layers | |
| num_key_value_heads = self.config.num_key_value_heads | |
| num_attention_heads = self.config.num_attention_heads | |
| intermediate_size = self.config.intermediate_size | |
| head_dim = hidden_size // num_attention_heads | |
| q_size = num_attention_heads * head_dim | |
| k_size = num_key_value_heads * head_dim | |
| v_size = num_key_value_heads * head_dim | |
| # non-attn per layer parm | |
| # llama use SwiGelu, gate, having up and down linear layer in mlp | |
| mlp_N = hidden_size * intermediate_size * 3 | |
| attn_linear_N = hidden_size * (q_size + k_size + v_size + num_attention_heads * head_dim) | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # non-attn all_layer parm | |
| dense_N = (mlp_N + attn_linear_N) * num_hidden_layers + lm_head_N | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * dense_N * tokens_sum | |
| # attn all_layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_attention_heads * num_hidden_layers | |
| # all_layer & all_token fwd & bwd flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def _estimate_llama_flops(self, tokens_sum, batch_seqlens, delta_time): | |
| hidden_size = self.config.hidden_size | |
| vocab_size = self.config.vocab_size | |
| num_hidden_layers = self.config.num_hidden_layers | |
| num_key_value_heads = self.config.num_key_value_heads | |
| num_attention_heads = self.config.num_attention_heads | |
| intermediate_size = self.config.intermediate_size | |
| head_dim = hidden_size // num_attention_heads | |
| q_size = num_attention_heads * head_dim | |
| k_size = num_key_value_heads * head_dim | |
| v_size = num_key_value_heads * head_dim | |
| # non-attn per layer parm | |
| # llama use SwiGelu, gate, having up and down linear layer in mlp | |
| mlp_N = hidden_size * intermediate_size * 3 | |
| attn_linear_N = hidden_size * (q_size + k_size + v_size + num_attention_heads * head_dim) | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # non-attn all_layer parm | |
| dense_N = (mlp_N + attn_linear_N) * num_hidden_layers + lm_head_N | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * dense_N * tokens_sum | |
| # attn all_layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_attention_heads * num_hidden_layers | |
| # all_layer & all_token fwd & bwd flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def _estimate_qwen2_vl_flops(self, tokens_sum, batch_seqlens, delta_time, **kargs): | |
| hidden_size = self.config.text_config.hidden_size | |
| vocab_size = self.config.text_config.vocab_size | |
| num_hidden_layers = self.config.text_config.num_hidden_layers | |
| num_key_value_heads = self.config.text_config.num_key_value_heads | |
| num_attention_heads = self.config.text_config.num_attention_heads | |
| intermediate_size = self.config.text_config.intermediate_size | |
| head_dim = hidden_size // num_attention_heads | |
| q_size = num_attention_heads * head_dim | |
| k_size = num_key_value_heads * head_dim | |
| v_size = num_key_value_heads * head_dim | |
| # non-attn per layer parm | |
| mlp_N = hidden_size * intermediate_size * 3 | |
| attn_linear_N = hidden_size * (q_size + k_size + v_size + num_attention_heads * head_dim) | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # non-attn all_layer parm | |
| dense_N = (mlp_N + attn_linear_N) * num_hidden_layers + lm_head_N | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * dense_N * tokens_sum | |
| # attn all_layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_attention_heads * num_hidden_layers | |
| # vit flops | |
| images_seqlens = kargs.get("images_seqlens", None) | |
| if images_seqlens is not None: | |
| vit_flops = self._estimate_qwen_vit_flop(images_seqlens, self.config.vision_config) | |
| else: | |
| vit_flops = 0 | |
| # all_layer & all_token fwd & bwd flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops + vit_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def _estimate_qwen3_vl_flops(self, tokens_sum, batch_seqlens, delta_time, **kargs): | |
| # qwen3_vl uses text_config and vision_config to distinguish configs of different parts. | |
| hidden_size = self.config.text_config.hidden_size | |
| vocab_size = self.config.text_config.vocab_size | |
| num_hidden_layers = self.config.text_config.num_hidden_layers | |
| num_key_value_heads = self.config.text_config.num_key_value_heads | |
| num_attention_heads = self.config.text_config.num_attention_heads | |
| intermediate_size = self.config.text_config.intermediate_size | |
| head_dim = hidden_size // num_attention_heads | |
| q_size = num_attention_heads * head_dim | |
| k_size = num_key_value_heads * head_dim | |
| v_size = num_key_value_heads * head_dim | |
| # non-attn per layer parm | |
| mlp_N = hidden_size * intermediate_size * 3 | |
| attn_linear_N = hidden_size * (q_size + k_size + v_size + num_attention_heads * head_dim) | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # non-attn all_layer parm | |
| dense_N = (mlp_N + attn_linear_N) * num_hidden_layers + lm_head_N | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * dense_N * tokens_sum | |
| # qwen3_vl uses deepstack to merge visual embeds and text embeds, but it has no tensor operation. | |
| # attn all_layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_attention_heads * num_hidden_layers | |
| # vit flops | |
| images_seqlens = kargs.get("images_seqlens", None) | |
| if images_seqlens is not None: | |
| vit_flops = self._estimate_qwen3_vit_flop(images_seqlens, self.config.vision_config) | |
| else: | |
| vit_flops = 0 | |
| # all_layer & all_token fwd & bwd flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops + vit_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def _estimate_qwen3_vl_moe_flops(self, tokens_sum, batch_seqlens, delta_time, **kargs): | |
| # qwen3_vl uses text_config and vision_config to distinguish configs of different parts. | |
| hidden_size = self.config.text_config.hidden_size | |
| vocab_size = self.config.text_config.vocab_size | |
| moe_intermediate_size = self.config.text_config.moe_intermediate_size | |
| num_hidden_layers = self.config.text_config.num_hidden_layers | |
| num_key_value_heads = self.config.text_config.num_key_value_heads | |
| num_attention_heads = self.config.text_config.num_attention_heads | |
| moe_intermediate_size = self.config.text_config.moe_intermediate_size | |
| moe_num_expert = self.config.text_config.num_experts | |
| moe_topk = self.config.text_config.num_experts_per_tok | |
| head_dim = getattr( | |
| self.config.text_config, | |
| "head_dim", | |
| self.config.text_config.hidden_size // self.config.text_config.num_attention_heads, | |
| ) | |
| q_size = num_attention_heads * head_dim | |
| k_size = num_key_value_heads * head_dim | |
| v_size = num_key_value_heads * head_dim | |
| # non-attn per layer parm | |
| moe_gata_N = hidden_size * moe_num_expert | |
| # moe has gate_proj, up_proj and down_proj using SwiGLU in ExpertMlp layer & shared experts | |
| moe_expertmlp_N = hidden_size * moe_intermediate_size * (moe_topk) * 3 | |
| attn_linear_N = hidden_size * (q_size + k_size + v_size + num_attention_heads * head_dim) | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # non-attn all_layer parm | |
| moe_N = (moe_gata_N + moe_expertmlp_N + attn_linear_N) * (num_hidden_layers) + lm_head_N | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * moe_N * tokens_sum | |
| # attn all_layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_attention_heads * num_hidden_layers | |
| # all_layer & all_token fwd & bwk flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| # vit flops | |
| images_seqlens = kargs.get("images_seqlens", None) | |
| if images_seqlens is not None: | |
| vit_flops = self._estimate_qwen3_vit_flop(images_seqlens, self.config.vision_config) | |
| else: | |
| vit_flops = 0 | |
| # all_layer & all_token fwd & bwd flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops + vit_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def _estimate_qwen3_vit_flop(self, images_seqlens, config): | |
| """ | |
| Estimate the FLOPS of the vision encoder for Qwen2 and Qwen2.5 | |
| """ | |
| if config is None: | |
| return 0 | |
| tokens_sum = sum(images_seqlens) | |
| num_heads = config.num_heads | |
| depth = config.depth | |
| dim = config.hidden_size | |
| mlp_hidden_dim = config.intermediate_size | |
| out_hidden_size = config.out_hidden_size | |
| spatial_merge_size = config.spatial_merge_size | |
| head_dim = dim // num_heads | |
| # every vision token's patch_embed comes from a conv of (C, T, H, W) -> (dim,) | |
| patch_embed_N = dim * config.in_channels * config.temporal_patch_size * config.patch_size * config.patch_size | |
| # Qwen3 VL vision mlp does not use GLU, thus 2. | |
| mlp_N = dim * mlp_hidden_dim * 2 | |
| attn_linear_N = dim * (4 * dim) # qkv and output proj | |
| merger_N = (out_hidden_size + (dim * (spatial_merge_size**2))) * (dim * (spatial_merge_size**2)) | |
| # Qwen3 VL uses deep stack, one merger for every deepstack layer | |
| deepstack_merger_N = merger_N * len(config.deepstack_visual_indexes) | |
| # non-attn all_layer parm | |
| dense_N = patch_embed_N + (mlp_N + attn_linear_N) * depth + deepstack_merger_N + merger_N | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * dense_N * tokens_sum | |
| # In Qwen3 VL, full attention is used in all vision layers. | |
| full_attn_layer_num = depth | |
| # full attn layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in images_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_heads * full_attn_layer_num | |
| vit_flops = dense_N_flops + attn_qkv_flops | |
| return vit_flops | |
| def _estimate_qwen_vit_flop(self, images_seqlens, config): | |
| """ | |
| Estimate the FLOPS of the vision encoder for Qwen2 and Qwen2.5 | |
| """ | |
| if config is None: | |
| return 0 | |
| tokens_sum = sum(images_seqlens) | |
| num_heads = config.num_heads | |
| depth = config.depth | |
| # In Qwen2 VL and Qwen2.5VL, the parameters naming are different: | |
| # | |
| # Parameter | Qwen2 VL | Qwen2.5 VL | |
| # --------------------------|------------------|------------------ | |
| # ViT hidden dimension | embed_dim | hidden_size | |
| # ViT output dimension | hidden_size | out_hidden_size | |
| # ViT MLP intermediate dim | embed_dim * mlp_ratio | intermediate_size | |
| # | |
| # See https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct/blob/main/config.json | |
| # and https://huggingface.co/Qwen/Qwen2-VL-7B-Instruct/blob/main/config.json for an example. | |
| is_qwen2_vl = hasattr(config, "embed_dim") | |
| dim = config.embed_dim if is_qwen2_vl else config.hidden_size | |
| mlp_hidden_dim = int(config.embed_dim * config.mlp_ratio) if is_qwen2_vl else config.intermediate_size | |
| out_hidden_size = config.hidden_size if is_qwen2_vl else config.out_hidden_size | |
| spatial_merge_size = config.spatial_merge_size | |
| head_dim = dim // num_heads | |
| # Qwen 2.5 VL uses SiLU, thus 3. | |
| mlp_N = dim * mlp_hidden_dim * (2 if is_qwen2_vl else 3) | |
| attn_linear_N = dim * (4 * dim) # qkv and output proj | |
| patch_embed_and_merger_N = (out_hidden_size + (dim * (spatial_merge_size**2))) * ( | |
| dim * (spatial_merge_size**2) | |
| ) | |
| # non-attn all_layer parm | |
| dense_N = (mlp_N + attn_linear_N) * depth + patch_embed_and_merger_N | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * dense_N * tokens_sum | |
| # In Qwen2.5 VL, windowed attention is used in some layers. | |
| full_attn_layer_num = config.depth if is_qwen2_vl else len(config.fullatt_block_indexes) | |
| window_attn_layer_num = config.depth - full_attn_layer_num | |
| # full attn layer & all_token fwd & bwd flops | |
| seqlen_square_sum = 0 | |
| for seqlen in images_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_heads * full_attn_layer_num | |
| # If window attention is used, add the window attention flops | |
| if window_attn_layer_num > 0: | |
| window_attn_compute_flops = 12 * tokens_sum * (config.window_size**2) * head_dim * num_heads | |
| attn_qkv_flops += window_attn_compute_flops * window_attn_layer_num | |
| vit_flops = dense_N_flops + attn_qkv_flops | |
| return vit_flops | |
| def _compute_hybrid_attn_params(config): | |
| """ | |
| Compute hybrid attention (full + GatedDeltaNet) linear param count and layer info. | |
| Layers alternate between full attention and GatedDeltaNet (linear attention) in groups | |
| of `full_attention_interval` layers: (full_attention_interval - 1) linear layers followed | |
| by 1 full attention layer. | |
| Full attention (Qwen3_5Attention) projections: | |
| q_proj: hidden_size -> num_attention_heads * head_dim (output gate ignored, see note) | |
| k_proj: hidden_size -> num_key_value_heads * head_dim | |
| v_proj: hidden_size -> num_key_value_heads * head_dim | |
| o_proj: num_attention_heads * head_dim -> hidden_size | |
| Note: q_proj actually outputs 2x (half query, half gate via sigmoid), but the gate | |
| contribution is ignored here for consistency with existing qwen3_next estimation. | |
| GatedDeltaNet (Qwen3_5GatedDeltaNet) projections: | |
| in_proj_qkv: hidden_size -> 2 * linear_k_size + linear_v_size | |
| in_proj_z: hidden_size -> linear_v_size (output gate) | |
| in_proj_b: hidden_size -> linear_num_value_heads (beta/gating scalar per head) | |
| in_proj_a: hidden_size -> linear_num_value_heads (alpha/decay scalar per head) | |
| out_proj: linear_v_size -> hidden_size | |
| conv1d: depthwise, channels = 2 * linear_k_size + linear_v_size, kernel = conv_kernel_dim | |
| where: | |
| linear_k_size = linear_num_key_heads * linear_key_head_dim | |
| linear_v_size = linear_num_value_heads * linear_value_head_dim | |
| This only counts projection and conv1d parameter FLOPs. The GatedDeltaNet | |
| recurrence FLOPs are computed separately by _compute_gdn_recurrence_flops. | |
| """ | |
| hidden_size = config.hidden_size | |
| num_attention_heads = config.num_attention_heads | |
| num_key_value_heads = config.num_key_value_heads | |
| head_dim = getattr(config, "head_dim", hidden_size // num_attention_heads) | |
| q_size = num_attention_heads * head_dim | |
| k_size = num_key_value_heads * head_dim | |
| v_size = num_key_value_heads * head_dim | |
| full_attention_interval = config.full_attention_interval | |
| num_full_attn_layers = config.num_hidden_layers // full_attention_interval | |
| # Full attention: q_proj + k_proj + v_proj + o_proj | |
| full_attn_linear_N = hidden_size * (q_size + k_size + v_size + num_attention_heads * head_dim) | |
| # GatedDeltaNet linear projections and depthwise conv1d | |
| linear_k_size = config.linear_num_key_heads * config.linear_key_head_dim | |
| linear_v_size = config.linear_num_value_heads * config.linear_value_head_dim | |
| # in_proj_qkv + in_proj_z + in_proj_b + in_proj_a + out_proj | |
| # (2 * linear_k_size + linear_v_size) + linear_v_size + linear_num_value_heads * 2 + linear_v_size | |
| linear_attn_size = 2 * linear_k_size + 2 * linear_v_size + 2 * config.linear_num_value_heads + linear_v_size | |
| # depthwise conv1d: each of (2 * linear_k_size + linear_v_size) channels has its own kernel | |
| conv_N = config.linear_conv_kernel_dim * (2 * linear_k_size + linear_v_size) | |
| linear_attn_linear_N = hidden_size * linear_attn_size + conv_N | |
| # Each group has 1 full attention layer + (full_attention_interval - 1) GatedDeltaNet layers | |
| num_linear_attn_per_group = full_attention_interval - 1 | |
| attn_linear_N = (full_attn_linear_N + num_linear_attn_per_group * linear_attn_linear_N) * num_full_attn_layers | |
| return attn_linear_N, num_full_attn_layers, head_dim, num_attention_heads | |
| def _compute_gdn_recurrence_flops(config, tokens_sum, num_full_attn_layers): | |
| """ | |
| Compute FLOPs for the GatedDeltaNet recurrence across all GDN layers. | |
| The recurrent form of GatedDeltaNet (ref: https://kexue.fm/archives/11033, eq.17/18): | |
| S_t = gamma_t * S_{t-1} + eta_t * (v_t - S_{t-1} @ k_t) @ k_t^T | |
| o_t = S_t @ q_t | |
| where S_t is the state matrix of shape (linear_value_head_dim, linear_key_head_dim) | |
| per value head. | |
| Note: in practice, training uses the chunked implementation (chunk_gated_delta_rule) | |
| which reorganizes the computation into chunk-level matrix multiplications for better | |
| hardware utilization. However, chunking is purely an implementation optimization that | |
| does not change the total arithmetic — it computes the same result as the recurrent | |
| form. We therefore use the recurrent form as the theoretical FLOPs baseline. | |
| Per step per head, the dominant ops (forward) are: | |
| S_{t-1} @ k_t (mat-vec, (d_v,d_k)@(d_k,)=(d_v,)): 2 * d_v * d_k FLOPs | |
| (...) @ k_t^T (outer product, (d_v,)⊗(d_k,)=(d_v,d_k)): d_v * d_k FLOPs | |
| o_t = S_t @ q_t (mat-vec, (d_v,d_k)@(d_k,)=(d_v,)): 2 * d_v * d_k FLOPs | |
| where d_v = linear_value_head_dim, d_k = linear_key_head_dim. | |
| Following the same convention as quadratic attention (Q@K + attn@V): | |
| fwd: (2 + 1 + 2) * d_v * d_k = 5 * d_v * d_k per step per head | |
| fwd + bwd (3x): 15 * d_v * d_k per step per head | |
| """ | |
| num_gdn_layers = config.num_hidden_layers - num_full_attn_layers | |
| return ( | |
| 15 | |
| * config.linear_key_head_dim | |
| * config.linear_value_head_dim | |
| * config.linear_num_value_heads | |
| * tokens_sum | |
| * num_gdn_layers | |
| ) | |
| def _estimate_qwen3_next_flops(self, tokens_sum, batch_seqlens, delta_time): | |
| """ | |
| Estimate the FLOPS of the Qwen3 Next model. | |
| """ | |
| hidden_size = self.config.hidden_size | |
| vocab_size = self.config.vocab_size | |
| num_hidden_layers = self.config.num_hidden_layers | |
| # hybrid attention params | |
| attn_linear_N, num_full_attn_layers, head_dim, num_attention_heads = self._compute_hybrid_attn_params( | |
| self.config | |
| ) | |
| # moe per layer parm | |
| # TopkGate layer and gate_proj, up_proj and down_proj using SwiGLU in ExpertMlp layer & shared experts | |
| moe_gata_N = hidden_size * self.config.num_experts | |
| moe_sharedexpertmlp_N = hidden_size * self.config.shared_expert_intermediate_size * 3 | |
| moe_expertmlp_N = hidden_size * self.config.moe_intermediate_size * self.config.num_experts_per_tok * 3 | |
| moe_N = (moe_gata_N + moe_expertmlp_N + moe_sharedexpertmlp_N) * num_hidden_layers | |
| # lm head param | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # non-attn all_layer & all_token fwd & bwd flops | |
| dense_N_flops = 6 * (moe_N + attn_linear_N + lm_head_N) * tokens_sum | |
| # attn all_layer & all_token fwd & bwd flops, only count full attention layers | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_attention_heads * num_full_attn_layers | |
| # GatedDeltaNet recurrence flops (state update + query, for all GDN layers) | |
| gdn_recurrence_flops = self._compute_gdn_recurrence_flops(self.config, tokens_sum, num_full_attn_layers) | |
| # all_layer & all_token fwd & bwd flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops + gdn_recurrence_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def _estimate_qwen3_5_family_flops(self, tokens_sum, batch_seqlens, delta_time, **kargs): | |
| """ | |
| Estimate the FLOPS of the Qwen3.5 model family (dense/MoE MLP + hybrid attention + ViT). | |
| Handles both Qwen3.5 (dense) and Qwen3.5-MoE by checking for MoE-specific config | |
| attributes. Both variants share hybrid attention and ViT; only the MLP differs. | |
| Text model (from text_config): | |
| Dense MLP per layer (SwiGLU, 3 projections): | |
| gate_proj: hidden_size -> intermediate_size | |
| up_proj: hidden_size -> intermediate_size | |
| down_proj: intermediate_size -> hidden_size | |
| MoE per layer (when num_experts is present): | |
| TopkGate router: hidden_size -> num_experts | |
| Routed experts (top-k activated, each SwiGLU): | |
| gate_proj: hidden_size -> moe_intermediate_size | |
| up_proj: hidden_size -> moe_intermediate_size | |
| down_proj: moe_intermediate_size -> hidden_size | |
| -> 3 projections * num_experts_per_tok active experts | |
| Shared expert (always active, SwiGLU): | |
| gate_proj: hidden_size -> shared_expert_intermediate_size | |
| up_proj: hidden_size -> shared_expert_intermediate_size | |
| down_proj: shared_expert_intermediate_size -> hidden_size | |
| Hybrid attention: see _compute_hybrid_attn_params docstring. | |
| LM head: | |
| lm_head: hidden_size -> vocab_size | |
| embed_tokens is an embedding table lookup and is excluded from FLOPs. | |
| Quadratic attention FLOPs (only full attention layers): | |
| Per layer: 2 * seq_len^2 * head_dim * num_attention_heads (Q@K + attn@V) | |
| fwd + bwd (3x) -> 6x total -> coefficient 12 | |
| Vision encoder: delegates to _estimate_qwen3_vit_flop. | |
| """ | |
| text_config = self.config.text_config if hasattr(self.config, "text_config") else self.config | |
| hidden_size = text_config.hidden_size | |
| vocab_size = text_config.vocab_size | |
| num_hidden_layers = text_config.num_hidden_layers | |
| # hybrid attention linear projection params (full + GatedDeltaNet) | |
| attn_linear_N, num_full_attn_layers, head_dim, num_attention_heads = self._compute_hybrid_attn_params( | |
| text_config | |
| ) | |
| # MLP params: MoE or dense depending on config | |
| is_moe = hasattr(text_config, "num_experts") | |
| if is_moe: | |
| # MoE per layer: router gate + routed expert MLPs (top-k) + shared expert MLP | |
| moe_gata_N = hidden_size * text_config.num_experts | |
| moe_expertmlp_N = hidden_size * text_config.moe_intermediate_size * text_config.num_experts_per_tok * 3 | |
| moe_sharedexpertmlp_N = hidden_size * text_config.shared_expert_intermediate_size * 3 | |
| mlp_N = (moe_gata_N + moe_expertmlp_N + moe_sharedexpertmlp_N) * num_hidden_layers | |
| else: | |
| # dense MLP per layer: gate_proj + up_proj + down_proj (SwiGLU) | |
| mlp_N = hidden_size * text_config.intermediate_size * 3 * num_hidden_layers | |
| # lm_head only; embed_tokens is a table lookup. | |
| lm_head_N = self._compute_lm_head_params(hidden_size, vocab_size) | |
| # linear projection flops: 6 (fwd + bwd) * params * tokens | |
| dense_N_flops = 6 * (mlp_N + attn_linear_N + lm_head_N) * tokens_sum | |
| # quadratic attention flops (Q@K and attn@V), only for full attention layers | |
| seqlen_square_sum = 0 | |
| for seqlen in batch_seqlens: | |
| seqlen_square_sum += seqlen * seqlen | |
| attn_qkv_flops = 12 * seqlen_square_sum * head_dim * num_attention_heads * num_full_attn_layers | |
| # GatedDeltaNet recurrence flops (state update + query, for all GDN layers) | |
| gdn_recurrence_flops = self._compute_gdn_recurrence_flops(text_config, tokens_sum, num_full_attn_layers) | |
| # vit flops (Qwen3-VL ViT) | |
| images_seqlens = kargs.get("images_seqlens", None) | |
| if images_seqlens is not None: | |
| vit_flops = self._estimate_qwen3_vit_flop(images_seqlens, self.config.vision_config) | |
| else: | |
| vit_flops = 0 | |
| # all_layer & all_token fwd & bwd flops | |
| flops_all_token = dense_N_flops + attn_qkv_flops + gdn_recurrence_flops + vit_flops | |
| flops_achieved = flops_all_token * (1.0 / delta_time) / 1e12 | |
| return flops_achieved | |
| def estimate_flops(self, batch_seqlens, delta_time, **kwargs): | |
| """ | |
| Estimate the FLOPS based on the number of valid tokens in the current batch and the time taken. | |
| Args: | |
| batch_seqlens (List[int]): A list where each element represents the number of valid tokens in the current batch. | |
| delta_time (float): The time taken to process the batch, in seconds. | |
| Returns: | |
| estimated_flops (float): The estimated FLOPS based on the input tokens and time. | |
| promised_flops (float): The expected FLOPS of the current device. | |
| """ | |
| tokens_sum = sum(batch_seqlens) | |
| func = self.estimate_func.get(self.config.model_type, self._estimate_unknown_flops) | |
| estimated_flops = func(tokens_sum, batch_seqlens, delta_time, **kwargs) | |
| promised_flops = get_device_flops() | |
| return estimated_flops, promised_flops | |