Upload 2 files
Browse files- final_report.py +121 -0
- see_layers.py +110 -0
final_report.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 3 |
+
from peft import PeftModel
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
# --- ⚙️ 配置区 ---
|
| 7 |
+
base_model_path = "./models/gemma-3-270m-it"
|
| 8 |
+
lora_path = "./tmodels/gemma-3-270m-it-FT-lora"
|
| 9 |
+
test_prompt = "you are fox,give say a ..."
|
| 10 |
+
# -----------------
|
| 11 |
+
|
| 12 |
+
def inject_chat_template(tokenizer):
|
| 13 |
+
tokenizer.chat_template = "{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/model/user/model/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '<start_of_turn>user\n' + message['content'] | trim + '<end_of_turn>\n' }}{% elif message['role'] == 'model' %}{{ '<start_of_turn>model\n' + message['content'] | trim + '<end_of_turn>\n' }}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ '<start_of_turn>model\n' }}{% endif %}"
|
| 14 |
+
|
| 15 |
+
def print_top_k(probs, tokenizer, k=5):
|
| 16 |
+
top_probs, top_indices = torch.topk(probs, k)
|
| 17 |
+
for i in range(k):
|
| 18 |
+
token = tokenizer.decode([top_indices[i]]).replace('\n', '\\n')
|
| 19 |
+
print(f" - Rank {i+1}: [{token}] \t 概率: {top_probs[i].item()*100:.2f}%")
|
| 20 |
+
|
| 21 |
+
# 💥【修正点】函数名已从 generate_decision_chain_report 改为 full_audit_analysis
|
| 22 |
+
def full_audit_analysis(model_name, model, tokenizer, prompt):
|
| 23 |
+
print("\n" + "="*80)
|
| 24 |
+
print(f"📄 开始对模型 [{model_name}] 进行终极决策链审计")
|
| 25 |
+
print("="*80)
|
| 26 |
+
|
| 27 |
+
# --- 准备工作 ---
|
| 28 |
+
messages = [{"role": "user", "content": prompt}]
|
| 29 |
+
input_text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 30 |
+
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
|
| 31 |
+
|
| 32 |
+
with torch.no_grad():
|
| 33 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 34 |
+
|
| 35 |
+
hidden_states = outputs.hidden_states
|
| 36 |
+
|
| 37 |
+
if isinstance(model, PeftModel):
|
| 38 |
+
base_model_inner = model.base_model.model
|
| 39 |
+
else:
|
| 40 |
+
base_model_inner = model
|
| 41 |
+
final_norm_layer = base_model_inner.model.norm
|
| 42 |
+
lm_head_layer = base_model_inner.lm_head
|
| 43 |
+
|
| 44 |
+
# --- 开始逐帧回放 ---
|
| 45 |
+
|
| 46 |
+
# 阶段 1 & 2: 从 Input 到 Layer 18 Raw
|
| 47 |
+
print("\n[阶段 1 & 2] 从输入到 Layer 18 Raw (部门主管的最终提案形成过程)")
|
| 48 |
+
print("-" * 80)
|
| 49 |
+
print("这是每一层计算完毕后,未经任何修正的“原始念头”:")
|
| 50 |
+
for i, layer_hidden in enumerate(hidden_states):
|
| 51 |
+
raw_vec = layer_hidden[0, -1, :].to(base_model_inner.dtype)
|
| 52 |
+
raw_logits = lm_head_layer(raw_vec)
|
| 53 |
+
raw_probs = F.softmax(raw_logits, dim=-1)
|
| 54 |
+
top_prob, top_id = torch.topk(raw_probs, 1)
|
| 55 |
+
top_word = tokenizer.decode([top_id[0]]).replace('\n', '\\n')
|
| 56 |
+
|
| 57 |
+
layer_name = "Embed (Raw)" if i == 0 else f"L-{i} (RAW)"
|
| 58 |
+
print(f" - {layer_name:<12}: 最可能的词是 [{top_word}] ({top_prob[0].item()*100:.1f}%)")
|
| 59 |
+
print("-" * 80)
|
| 60 |
+
|
| 61 |
+
# 阶段 3: Layer 18 Raw -> Final Norm -> Normalized Vector
|
| 62 |
+
print("\n[阶段 3] Layer 18 Raw -> Final Norm (技术总监审查并修改提案)")
|
| 63 |
+
print("-" * 80)
|
| 64 |
+
raw_last_layer_vec = hidden_states[-1][0, -1, :].to(base_model_inner.dtype)
|
| 65 |
+
normalized_vec = final_norm_layer(raw_last_layer_vec)
|
| 66 |
+
cos_sim = F.cosine_similarity(raw_last_layer_vec.unsqueeze(0), normalized_vec.unsqueeze(0))
|
| 67 |
+
print("1. 部门主管 (L-18 Raw) 提交的原始提案翻译如下:")
|
| 68 |
+
print_top_k(F.softmax(lm_head_layer(raw_last_layer_vec), dim=-1), tokenizer)
|
| 69 |
+
print(f"\n2. 技术总监 (Final Norm) 对提案向量进行了修正。")
|
| 70 |
+
print(f" (向量方向偏移度: {cos_sim.item():.4f}, 1.0 表示未修正)")
|
| 71 |
+
print("-" * 80)
|
| 72 |
+
|
| 73 |
+
# 阶段 4: Normalized Vector -> LM Head -> Logits
|
| 74 |
+
print("\n[阶段 4] Normalized Vector -> LM Head (秘书处将修改后的提案翻译成具体方案)")
|
| 75 |
+
print("-" * 80)
|
| 76 |
+
print("技术总监修正后的提案,经秘书处翻译,内容变为:")
|
| 77 |
+
normed_logits = lm_head_layer(normalized_vec)
|
| 78 |
+
normed_probs = F.softmax(normed_logits, dim=-1)
|
| 79 |
+
print_top_k(normed_probs, tokenizer)
|
| 80 |
+
print("-" * 80)
|
| 81 |
+
|
| 82 |
+
# 阶段 5: Logits -> Decoding Strategy -> Final Token
|
| 83 |
+
print("\n[阶段 5] CEO (Decoding Strategy) 结合所有信息做出最终裁决")
|
| 84 |
+
print("-" * 80)
|
| 85 |
+
print("1. CEO 在做决定前,参考的最终概率分布 (outputs.logits) 是:")
|
| 86 |
+
final_logits_for_generation = outputs.logits[0, -1, :]
|
| 87 |
+
final_probs_for_generation = F.softmax(final_logits_for_generation, dim=-1)
|
| 88 |
+
print_top_k(final_probs_for_generation, tokenizer)
|
| 89 |
+
|
| 90 |
+
print("\n2. 经过对上下文、风险和连贯性的最终权衡,CEO 发表了公开声明:")
|
| 91 |
+
gen_output = model.generate(
|
| 92 |
+
**inputs,
|
| 93 |
+
max_new_tokens=100,
|
| 94 |
+
do_sample=False,
|
| 95 |
+
repetition_penalty=1.1
|
| 96 |
+
)
|
| 97 |
+
response = tokenizer.decode(gen_output[0][inputs.input_ids.shape[-1]:], skip_special_tokens=True)
|
| 98 |
+
print(" >>> " + response.strip())
|
| 99 |
+
print("-" * 80)
|
| 100 |
+
print(f"✅ 模型 [{model_name}] 决策链审计完成。")
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
# --- 主程序 ---
|
| 104 |
+
print("🚀 启动终极决策链全景报告生成器...")
|
| 105 |
+
print(f"📝 测试 Prompt: '{test_prompt}'")
|
| 106 |
+
|
| 107 |
+
tokenizer = AutoTokenizer.from_pretrained(base_model_path)
|
| 108 |
+
inject_chat_template(tokenizer)
|
| 109 |
+
|
| 110 |
+
# --- 审计 Base-IT 模型 ---
|
| 111 |
+
base_model = AutoModelForCausalLM.from_pretrained(base_model_path, device_map="auto")
|
| 112 |
+
full_audit_analysis("Base-IT (老黄牛)", base_model, tokenizer, test_prompt)
|
| 113 |
+
|
| 114 |
+
# --- 审计 FT 模型 ---
|
| 115 |
+
base_model_for_ft = AutoModelForCausalLM.from_pretrained(base_model_path, device_map="auto")
|
| 116 |
+
ft_model = PeftModel.from_pretrained(base_model_for_ft, lora_path)
|
| 117 |
+
full_audit_analysis("FT (监工介入)", ft_model, tokenizer, test_prompt)
|
| 118 |
+
|
| 119 |
+
print("\n\n" + "="*80)
|
| 120 |
+
print("🎉 所有审计工作已完成。")
|
| 121 |
+
print("="*80)
|
see_layers.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 3 |
+
from peft import PeftModel
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
# --- ⚙️ 配置区 ---
|
| 7 |
+
base_model_path = "./models/gemma-3-270m-it" # 您的 Base-IT 模型路径
|
| 8 |
+
lora_path = "./tmodels/gemma-3-270m-it-FT-lora" # 您的 FT 模型路径
|
| 9 |
+
test_prompt = "给我讲个睡前故事"
|
| 10 |
+
print(f"问题:{test_prompt}")
|
| 11 |
+
# -----------------
|
| 12 |
+
|
| 13 |
+
def inject_chat_template(tokenizer):
|
| 14 |
+
# 手动注入 Gemma 3 模板,防止报错
|
| 15 |
+
tokenizer.chat_template = "{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/model/user/model/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '<start_of_turn>user\n' + message['content'] | trim + '<end_of_turn>\n' }}{% elif message['role'] == 'model' %}{{ '<start_of_turn>model\n' + message['content'] | trim + '<end_of_turn>\n' }}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ '<start_of_turn>model\n' }}{% endif %}"
|
| 16 |
+
|
| 17 |
+
def analyze_and_generate(model_name, model, tokenizer, prompt):
|
| 18 |
+
print(f"\n{'='*20} 分析模型: {model_name} {'='*20}")
|
| 19 |
+
|
| 20 |
+
# --- 1. 层级透镜 (Logit Lens) ---
|
| 21 |
+
messages = [{"role": "user", "content": prompt}]
|
| 22 |
+
input_text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 23 |
+
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
|
| 24 |
+
|
| 25 |
+
with torch.no_grad():
|
| 26 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 27 |
+
|
| 28 |
+
hidden_states = outputs.hidden_states
|
| 29 |
+
|
| 30 |
+
# 获取 Final Norm 和 Head
|
| 31 |
+
if isinstance(model, PeftModel):
|
| 32 |
+
base = model.base_model.model
|
| 33 |
+
else:
|
| 34 |
+
base = model
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
final_norm = base.model.norm
|
| 38 |
+
lm_head = base.lm_head
|
| 39 |
+
except:
|
| 40 |
+
final_norm = lambda x: x
|
| 41 |
+
lm_head = base.lm_head
|
| 42 |
+
|
| 43 |
+
print(f"\n🔍 [微观视角] 思维演变过程 (共 {len(hidden_states)-1} 层)")
|
| 44 |
+
# 打印表头
|
| 45 |
+
print(f"{'层数':<8} | {'Top1 词':<12} | {'概率':<6} | {'活跃词(>1%)':<10} | {'熵(混乱度)':<10} | {'Top 2-5 备选'}")
|
| 46 |
+
print("-" * 95)
|
| 47 |
+
|
| 48 |
+
for i, layer_hidden in enumerate(hidden_states):
|
| 49 |
+
last_token_vec = layer_hidden[0, -1, :].to(base.dtype)
|
| 50 |
+
|
| 51 |
+
# 模拟输出
|
| 52 |
+
normalized_vec = final_norm(last_token_vec)
|
| 53 |
+
logits = lm_head(normalized_vec)
|
| 54 |
+
probs = F.softmax(logits, dim=-1)
|
| 55 |
+
|
| 56 |
+
# --- 计算新指标 ---
|
| 57 |
+
# 1. 活跃词数: 概率大于 1% 的词有多少个
|
| 58 |
+
active_candidates = (probs > 0.01).sum().item()
|
| 59 |
+
|
| 60 |
+
# 2. 熵 (Entropy): 衡量混乱程度。值越大越混乱,值越小越确定。
|
| 61 |
+
# Formula: -sum(p * log(p))
|
| 62 |
+
entropy = -torch.sum(probs * torch.log(probs + 1e-9)).item()
|
| 63 |
+
|
| 64 |
+
# 获取 Top K
|
| 65 |
+
top_probs, top_indices = torch.topk(probs, 5)
|
| 66 |
+
top_words = [tokenizer.decode([idx]).replace('\n', '\\n') for idx in top_indices]
|
| 67 |
+
|
| 68 |
+
layer_name = "Embed" if i == 0 else f"L-{i}"
|
| 69 |
+
top1_prob = top_probs[0].item() * 100
|
| 70 |
+
others = ", ".join(top_words[1:])
|
| 71 |
+
|
| 72 |
+
# 格式化打印
|
| 73 |
+
print(f"{layer_name:<8} | {top_words[0]:<12} | {top1_prob:>5.1f}% | {active_candidates:<10} | {entropy:<10.4f} | {others}")
|
| 74 |
+
|
| 75 |
+
# --- 2. 最终生成 (Generation) ---
|
| 76 |
+
print(f"\n🗣️ [宏观视角] 最终完整回答")
|
| 77 |
+
print("-" * 50)
|
| 78 |
+
|
| 79 |
+
# 生成参数
|
| 80 |
+
gen_output = model.generate(
|
| 81 |
+
**inputs,
|
| 82 |
+
max_new_tokens=100,
|
| 83 |
+
do_sample=False, # 为了结果稳定,这里用贪婪搜索,看它最想说什么
|
| 84 |
+
repetition_penalty=1.1
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
response = tokenizer.decode(gen_output[0][inputs.input_ids.shape[-1]:], skip_special_tokens=True)
|
| 88 |
+
print(response.strip())
|
| 89 |
+
print("-" * 50)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# --- 主程序 ---
|
| 93 |
+
print("🚀 启动深度分析工具 v2...")
|
| 94 |
+
|
| 95 |
+
# 1. Base-IT 模型
|
| 96 |
+
tokenizer = AutoTokenizer.from_pretrained(base_model_path)
|
| 97 |
+
inject_chat_template(tokenizer)
|
| 98 |
+
base_model = AutoModelForCausalLM.from_pretrained(base_model_path, device_map="auto")
|
| 99 |
+
|
| 100 |
+
analyze_and_generate("Base-IT (老黄牛)", base_model, tokenizer, test_prompt)
|
| 101 |
+
|
| 102 |
+
# 2. FT 模型
|
| 103 |
+
# 注意:PeftModel 会修改原模型,所以最好重新加载一下 Base,或者直接套用
|
| 104 |
+
# 这里为了严谨,我们直接在 base_model 上加载适配器
|
| 105 |
+
print("\n... 正在加载 LoRA 适配器 ...")
|
| 106 |
+
ft_model = PeftModel.from_pretrained(base_model, lora_path)
|
| 107 |
+
|
| 108 |
+
analyze_and_generate("FT (监工介入)", ft_model, tokenizer, test_prompt)
|
| 109 |
+
|
| 110 |
+
print("\n✅ 所有测试完成。")
|