File size: 4,916 Bytes
1836a26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import torch
import torch.nn.functional as F
import math
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

# ==================== 1. 本地路径配置 ====================
base_model_path = "./Qwen3-4B-Thinking-2507"
lora_path = "./QiMing-Polaris-Qwen3-4B-Thinking-2507_burden_trained_lora"

# 测试 Prompt
prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.

### Instruction:
What is the 'Burden-based Training' method?

### Input:


### Response:
"""

print("🚀 启动逐层“活跃候选词数量”测算工具...")

# 2. 加载分词器和准备 Input
tokenizer = AutoTokenizer.from_pretrained(base_model_path, trust_remote_code=True)
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")


# ==================== Logit Lens 候选词测算函数 ====================
def count_active_words_per_layer(model, p_thresh=0.001):
    """
    p_thresh = 0.001 代表统计概率 > 0.1% 的所有活跃候选词数量
    """
    residual_outputs = {}
    hooks = []

    def make_hook(layer_idx):
        def hook(module, input_tensor, output_tensor):
            out = (
                output_tensor[0] if isinstance(output_tensor, tuple) else output_tensor
            )
            residual_outputs[layer_idx] = out.detach()

        return hook

    # 定位模型结构
    if hasattr(model, "model") and hasattr(model.model, "layers"):
        layers = model.model.layers
        final_norm = model.model.norm
        lm_head = model.lm_head
    elif hasattr(model, "base_model"):
        layers = model.base_model.model.model.layers
        final_norm = model.base_model.model.model.norm
        lm_head = model.base_model.model.lm_head
    else:
        raise AttributeError("无法定位模型层级结构")

    # 注册 Hook
    for i, layer in enumerate(layers):
        hooks.append(layer.register_forward_hook(make_hook(i)))

    # 前向传播
    with torch.no_grad():
        _ = model(**inputs)

    # 及时清理 Hook
    for h in hooks:
        h.remove()

    # 逐层计算 Logit Lens 映射
    metrics = []
    for i in range(len(layers)):
        # 拿到当前层最后一个 Token 的向量
        h_l = residual_outputs[i][0, -1, :].to(dtype=final_norm.weight.dtype)

        # 强制把当前层的向量过 Final Norm + LM Head,映射到整个词表上
        norm_h = final_norm(h_l)
        logits = lm_head(norm_h)
        probs = F.softmax(logits, dim=-1)

        # 1. 统计概率 > 0.1% 的活跃候选词个数
        active_count = (probs > p_thresh).sum().item()

        # 2. 计算有效候选词数 (e^Entropy)
        log_probs = F.log_softmax(logits, dim=-1)
        entropy = -(probs * log_probs).sum().item()
        effective_count = math.exp(entropy)  # 指数熵,代表“等效候选词个数”

        metrics.append(
            {
                "layer": i,
                "active_words": active_count,
                "effective_words": effective_count,
                "entropy": entropy,
            }
        )

    return metrics


# ==================== 3. 测量 Base 模型 ====================
print(f"\n🔍 正在测算 1/2: 原始模型 [{base_model_path}]...")
base_model = AutoModelForCausalLM.from_pretrained(
    base_model_path,
    torch_dtype=torch.bfloat16,
    device_map="cuda",
    trust_remote_code=True,
)
base_results = count_active_words_per_layer(base_model)


# ==================== 4. 挂载 FT LoRA 并测量 ====================
print(f"🔍 正在测算 2/2: FT 负重训练模型 [Base + {lora_path}]...")
ft_model = PeftModel.from_pretrained(base_model, lora_path)
ft_results = count_active_words_per_layer(ft_model)


# ==================== 5. 打印对比报告 ====================
print("\n" + "=" * 95)
print(
    f"{'层数':<6} | {'[Base] 活跃词数(>0.1%)':<20} | {'[FT负重] 活跃词数(>0.1%)':<20} | {'词数差值 (FT-Base)':<18} | {'找词/砍词趋势'}"
)
print("=" * 95)

for i in range(len(base_results)):
    b_cnt = base_results[i]["active_words"]
    ft_cnt = ft_results[i]["active_words"]
    diff = ft_cnt - b_cnt

    if diff > 0:
        trend = f"⬆️ FT 广搜找词更多 (+{diff})"
    elif diff < 0:
        trend = f"⬇️ FT 猛减砍词 ({diff})"
    else:
        trend = "➡️ 词数持平"

    print(f"L-{i:<3} | {b_cnt:<20} | {ft_cnt:<20} | {diff:<+18} | {trend}")

print("=" * 95)
print("💡 数据解读说明:")
print("1. [活跃词数(>0.1%)] 代表这一层模型脑子里存留的候选词个数。")
print(
    "2. 如果浅层显示 '⬆️ FT 广搜找词更多',证明 FT 在浅层把关联词全捞出来了(前面找全);"
)
print(
    "3. 如果中深层显示 '⬇️ FT 猛减砍词',证明 FT 在中深层下狠手把无用词砍光了(向下猛减)!"
)
print("=" * 95)