File size: 7,528 Bytes
5e27996
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import copy
from typing import List, Union, Dict

import PIL.Image
import torch
import numpy as np
import torchvision.transforms.functional as F
import transformers

from transformers import PreTrainedTokenizer

IGNORE_INDEX = -100

def print_trainable_params(model: torch.nn.Module) -> None:
    trainable_params, all_param = 0, 0
    for param in model.parameters():
        num_params = param.numel()
        # if using DS Zero 3 and the weights are initialized empty
        if num_params == 0 and hasattr(param, "ds_numel"):
            num_params = param.ds_numel
        all_param += num_params
        if param.requires_grad:
            trainable_params += num_params
    print("trainable params: {:d} || all params: {:d} || trainable%: {:.4f}".format(
        trainable_params, all_param, 100 * trainable_params / all_param))


def post_process_generate_ids(tokenizer: PreTrainedTokenizer, ids: torch.Tensor):
    ids = copy.deepcopy(ids)  # do not modify origin preds and targets
    ids[ids < 0] = tokenizer.pad_token_id
    # pad_to_multiof 开启后, 多余的部分没法解码, 这里暂时替换为 ','
    ids[ids >= len(tokenizer)] = tokenizer.convert_tokens_to_ids(',')
    return ids


def decode_generate_ids(tokenizer: PreTrainedTokenizer, ids: torch.Tensor) -> Union[List[str], str]:
    assert ids.ndim in [1, 2]
    only_one_sentence = ids.ndim == 1
    if only_one_sentence:
        ids = ids.unsqueeze(0)
    ids = post_process_generate_ids(tokenizer, ids)
    res = tokenizer.batch_decode(ids, skip_special_tokens=True, clean_up_tokenization_spaces=True)
    if only_one_sentence:
        return res[0]
    return res



def draw_bounding_boxes(
        image: Union[torch.Tensor, PIL.Image.Image],
        boxes: Union[torch.Tensor, List, np.ndarray],
        **kwargs,
):
    if isinstance(image, PIL.Image.Image):
        from torchvision.transforms import PILToTensor
        image = PILToTensor()(image)
    assert isinstance(image, torch.Tensor), ""

    if not isinstance(boxes, torch.Tensor):
        boxes = torch.as_tensor(boxes)
    assert isinstance(boxes, torch.Tensor)

    from torchvision.utils import draw_bounding_boxes as _draw_bounding_boxes
    return _draw_bounding_boxes(image, boxes, **kwargs)


# https://github.com/huggingface/tokenizers/issues/247#issuecomment-675458087
def smart_tokenizer_and_embedding_resize(
        special_tokens_dict: Dict,
        tokenizer: transformers.PreTrainedTokenizer,
        model: transformers.PreTrainedModel,
):
    """Resize tokenizer and embedding.

    Note: This is the unoptimized version that may make your embedding size not be divisible by 64.
    """
    num_new_tokens = tokenizer.add_special_tokens(special_tokens_dict)
    model.resize_token_embeddings(len(tokenizer))

    if num_new_tokens > 0:
        input_embeddings = model.get_input_embeddings().weight.data
        output_embeddings = model.get_output_embeddings().weight.data

        input_embeddings_avg = input_embeddings[:-num_new_tokens].mean(dim=0, keepdim=True)
        output_embeddings_avg = output_embeddings[:-num_new_tokens].mean(dim=0, keepdim=True)

        input_embeddings[-num_new_tokens:] = input_embeddings_avg
        output_embeddings[-num_new_tokens:] = output_embeddings_avg

def patch_transformer_logging():
    import logging
    import transformers
    def enable_explicit_format():
        handlers = transformers.utils.logging._get_library_root_logger().handlers

        for handler in handlers:
            formatter = logging.Formatter("[(%(levelname)s) %(pathname)s:%(lineno)s ] %(asctime)s >> %(message)s")
            handler.setFormatter(formatter)
    transformers.utils.logging.enable_explicit_format = enable_explicit_format

def print_model_stats(model):
    """
    通用模型参数统计工具。
    自动识别是 Dense 还是 MoE 模型,并计算 Total vs Active 参数量。
    """
    
    # 1. 计算物理总参数量 (Total Parameters)
    # 使用 set 避免计算共享参数 (Shared Weights),例如 Embedding 和 lm_head 共享权重的情况
    unique_params = {p.data_ptr(): p for p in model.parameters()}.values()
    total_params = sum(p.numel() for p in unique_params)
    
    # 2. 初始化激活参数量 (Active Parameters)
    # 默认假设是 Dense 模型,所有参数都是激活的
    active_params = total_params
    
    moe_infos = [] # 用于存储发现的 MoE 层信息
    
    # 3. 遍历所有子模块,寻找 MoE 层特征
    # 我们不匹配类名,而是匹配"特征" (Duck Typing)
    for name, module in model.named_modules():
        # 特征判定:有 num_experts 属性,且有一个叫 experts 的 ModuleList
        if hasattr(module, 'num_experts') and hasattr(module, 'experts') and isinstance(module.experts, nn.ModuleList):
            
            # 获取关键超参
            num_experts = getattr(module, 'num_experts', 0)
            # 兼容不同的 top_k 命名 (top_k 或 num_experts_per_tok)
            top_k = getattr(module, 'top_k', getattr(module, 'num_experts_per_tok', 0))
            
            # 如果找不到 top_k,可能不是标准的 Sparse MoE,跳过
            if top_k == 0: 
                continue

            # --- 核心计算逻辑 ---
            # 1. 计算单个专家的参数量 (假设所有专家结构相同,取第一个)
            # 这里必须用 recursion=True 确保统计专家内部所有层
            single_expert_params = sum(p.numel() for p in module.experts[0].parameters())
            
            # 2. 计算"休眠"专家数量
            dormant_experts = num_experts - top_k
            
            # 3. 从激活总数中扣除休眠专家的参数
            # 注意:total_params 里已经包含了 N 个专家,我们只需要减去 (N-K) 个
            if dormant_experts > 0:
                deduction = dormant_experts * single_expert_params
                active_params -= deduction
                
                moe_infos.append({
                    "layer": name,
                    "experts": num_experts,
                    "active": top_k,
                    "expert_size": single_expert_params
                })

    # --- 4. 格式化输出 ---
    def format_num(num):
        if num >= 1e9: return f"{num/1e9:.2f}B"
        if num >= 1e6: return f"{num/1e6:.2f}M"
        if num >= 1e3: return f"{num/1e3:.2f}K"
        return str(num)

    print("=" * 50)
    print(f"Model Architecture Analysis")
    print("=" * 50)
    
    if len(moe_infos) > 0:
        print(f"👉 Detection: MoE Model (Sparse Mixture-of-Experts)")
        print(f"   - Found {len(moe_infos)} MoE layers")
        print(f"   - Config: {moe_infos[0]['experts']} Experts, Top-{moe_infos[0]['active']} Active")
    else:
        print(f"👉 Detection: Dense Model (Standard Transformer)")
        
    print("-" * 50)
    print(f"Total Parameters (VRAM):  {format_num(total_params)}")
    print(f"Active Parameters (FLOPs): {format_num(active_params)}")
    
    if len(moe_infos) > 0:
        sparsity = 1 - (active_params / total_params)
        print(f"Sparsity Ratio:            {sparsity:.2%}")
        # 计算相比 Dense 版本的倍数
        # 假设 Dense 版本就是 active_params 大小(不太严谨但直观)
        print(f"Upcycling Scale:           {total_params/active_params:.2f}x Larger than Dense Base")
    else:
        print(f"Sparsity Ratio:            0.00% (Dense)")
        
    print("=" * 50)
    
    return total_params, active_params