Files

376 lines
14 KiB
Python

#!/usr/bin/env python
# coding=utf-8
"""
深入分析 Expert 17-24 产生 NaN 的原因
根据之前的调试日志,只有 Expert 17-24 这 8 个连续的 expert 产生 NaN。
本脚本尝试:
1. 分析哪些 token 激活了 Expert 17-24
2. 检查这些 token 的输入数据特征
3. 验证 Expert 17-24 的权重数据是否有异常
4. 手动执行 LoRA 计算,逐步定位 NaN 产生位置
"""
import os
import sys
import math
sys.path.insert(0, os.path.dirname(__file__) + "/../build")
import torch
import torch.nn.functional as F
import numpy as np
# 数据路径
DATA_PATH = "/mnt/data/lpl/kt_nan_debug_data.pt"
def silu(x):
"""SiLU activation function."""
return x * torch.sigmoid(x)
def load_and_analyze_data():
"""加载数据并进行详细分析"""
print(f"\n{'='*70}")
print("加载和分析 PT 文件数据")
print(f"{'='*70}")
data = torch.load(DATA_PATH)
# 配置
expert_num = data["expert_num"]
hidden_size = data["hidden_size"]
intermediate_size = data["intermediate_size"]
num_experts_per_tok = data["num_experts_per_tok"]
qlen = data["input_data"].shape[0]
lora_rank = data["gate_lora_a"].shape[1]
lora_alpha = 16.0
lora_scaling = lora_alpha / lora_rank
print(f"\n配置参数:")
print(f" expert_num: {expert_num}")
print(f" hidden_size: {hidden_size}")
print(f" intermediate_size: {intermediate_size}")
print(f" qlen: {qlen}")
print(f" num_experts_per_tok: {num_experts_per_tok}")
print(f" lora_rank: {lora_rank}")
print(f" lora_scaling: {lora_scaling}")
# 提取数据
input_data = data["input_data"].contiguous()
expert_ids = data["expert_ids"].contiguous()
weights = data["weights"].contiguous()
gate_proj = data["gate_proj"].contiguous()
up_proj = data["up_proj"].contiguous()
down_proj = data["down_proj"].contiguous()
gate_lora_a = data["gate_lora_a"].contiguous()
gate_lora_b = data["gate_lora_b"].contiguous()
up_lora_a = data["up_lora_a"].contiguous()
up_lora_b = data["up_lora_b"].contiguous()
down_lora_a = data["down_lora_a"].contiguous()
down_lora_b = data["down_lora_b"].contiguous()
return {
"input_data": input_data,
"expert_ids": expert_ids,
"weights": weights,
"gate_proj": gate_proj,
"up_proj": up_proj,
"down_proj": down_proj,
"gate_lora_a": gate_lora_a,
"gate_lora_b": gate_lora_b,
"up_lora_a": up_lora_a,
"up_lora_b": up_lora_b,
"down_lora_a": down_lora_a,
"down_lora_b": down_lora_b,
"config": {
"expert_num": expert_num,
"hidden_size": hidden_size,
"intermediate_size": intermediate_size,
"num_experts_per_tok": num_experts_per_tok,
"qlen": qlen,
"lora_rank": lora_rank,
"lora_scaling": lora_scaling,
},
}
def analyze_expert_routing(data):
"""分析 Expert 路由情况,特别关注 Expert 17-24"""
print(f"\n{'='*70}")
print("Expert 路由分析")
print(f"{'='*70}")
expert_ids = data["expert_ids"]
config = data["config"]
qlen = config["qlen"]
num_experts_per_tok = config["num_experts_per_tok"]
# 统计每个 expert 被激活的次数
expert_counts = {}
expert_token_map = {} # expert_id -> list of (token_idx, position_in_k)
for tok_idx in range(qlen):
for k_idx in range(num_experts_per_tok):
expert_id = expert_ids[tok_idx, k_idx].item()
if expert_id not in expert_counts:
expert_counts[expert_id] = 0
expert_token_map[expert_id] = []
expert_counts[expert_id] += 1
expert_token_map[expert_id].append((tok_idx, k_idx))
print(f"\n所有激活的 Expert (共 {len(expert_counts)} 个):")
for expert_id in sorted(expert_counts.keys()):
count = expert_counts[expert_id]
marker = " *** SUSPECT ***" if 17 <= expert_id <= 24 else ""
print(f" Expert {expert_id}: {count} tokens{marker}")
# 详细分析 Expert 17-24
print(f"\n{'='*70}")
print("Expert 17-24 详细分析")
print(f"{'='*70}")
problem_experts = list(range(17, 25))
for expert_id in problem_experts:
if expert_id in expert_token_map:
tokens = expert_token_map[expert_id]
print(f"\nExpert {expert_id}: 被 {len(tokens)} 个 token 激活")
print(f" 激活的 token (token_idx, k_position):")
for tok_idx, k_idx in tokens[:10]: # 只显示前 10 个
print(f" Token {tok_idx}, k={k_idx}")
if len(tokens) > 10:
print(f" ... 还有 {len(tokens) - 10} 个")
else:
print(f"\nExpert {expert_id}: 未被激活")
return expert_token_map
def check_data_for_expert(data, expert_id):
"""检查特定 Expert 的输入数据和权重是否有异常"""
print(f"\n{'='*70}")
print(f"Expert {expert_id} 数据检查")
print(f"{'='*70}")
config = data["config"]
# 检查基础权重
gate_proj = data["gate_proj"][expert_id]
up_proj = data["up_proj"][expert_id]
down_proj = data["down_proj"][expert_id]
print(f"\n基础权重检查:")
for name, w in [("gate_proj", gate_proj), ("up_proj", up_proj), ("down_proj", down_proj)]:
has_nan = torch.isnan(w).any().item()
has_inf = torch.isinf(w).any().item()
w_min = w.min().item()
w_max = w.max().item()
w_mean = w.float().mean().item()
w_std = w.float().std().item()
print(
f" {name}: NaN={has_nan}, Inf={has_inf}, range=[{w_min:.6f}, {w_max:.6f}], mean={w_mean:.6f}, std={w_std:.6f}"
)
# 检查 LoRA 权重
gate_lora_a = data["gate_lora_a"][expert_id]
gate_lora_b = data["gate_lora_b"][expert_id]
up_lora_a = data["up_lora_a"][expert_id]
up_lora_b = data["up_lora_b"][expert_id]
down_lora_a = data["down_lora_a"][expert_id]
down_lora_b = data["down_lora_b"][expert_id]
print(f"\nLoRA 权重检查:")
for name, w in [
("gate_lora_a", gate_lora_a),
("gate_lora_b", gate_lora_b),
("up_lora_a", up_lora_a),
("up_lora_b", up_lora_b),
("down_lora_a", down_lora_a),
("down_lora_b", down_lora_b),
]:
has_nan = torch.isnan(w).any().item()
has_inf = torch.isinf(w).any().item()
w_min = w.min().item()
w_max = w.max().item()
w_mean = w.float().mean().item()
w_std = w.float().std().item()
print(
f" {name}: NaN={has_nan}, Inf={has_inf}, range=[{w_min:.6f}, {w_max:.6f}], mean={w_mean:.6f}, std={w_std:.6f}"
)
return not any(
[
torch.isnan(gate_proj).any(),
torch.isnan(up_proj).any(),
torch.isnan(down_proj).any(),
torch.isnan(gate_lora_a).any(),
torch.isnan(gate_lora_b).any(),
torch.isnan(up_lora_a).any(),
torch.isnan(up_lora_b).any(),
torch.isnan(down_lora_a).any(),
torch.isnan(down_lora_b).any(),
]
)
def manual_forward_for_expert(data, expert_token_map, expert_id):
"""对单个 Expert 手动执行 forward 计算,逐步定位 NaN"""
print(f"\n{'='*70}")
print(f"Expert {expert_id} 手动 Forward 计算")
print(f"{'='*70}")
if expert_id not in expert_token_map:
print(f"Expert {expert_id} 未被激活,跳过")
return
config = data["config"]
tokens = expert_token_map[expert_id]
num_tokens = len(tokens)
print(f"Expert {expert_id} 处理 {num_tokens} 个 token")
# 收集该 expert 的输入
input_data = data["input_data"]
local_input = torch.stack([input_data[tok_idx] for tok_idx, _ in tokens])
# 获取权重
gate_proj = data["gate_proj"][expert_id] # [intermediate_size, hidden_size]
up_proj = data["up_proj"][expert_id]
gate_lora_a = data["gate_lora_a"][expert_id] # [lora_rank, hidden_size]
gate_lora_b = data["gate_lora_b"][expert_id] # [intermediate_size, lora_rank]
up_lora_a = data["up_lora_a"][expert_id]
up_lora_b = data["up_lora_b"][expert_id]
lora_scaling = config["lora_scaling"]
# Step 1: Base Gate GEMM
# [num_tokens, hidden_size] @ [hidden_size, intermediate_size] -> [num_tokens, intermediate_size]
gate_base = local_input.float() @ gate_proj.float().T
print(f"\nStep 1 - Gate Base GEMM:")
print(f" local_input: shape={local_input.shape}, NaN={torch.isnan(local_input).sum().item()}")
print(f" gate_proj: shape={gate_proj.shape}, NaN={torch.isnan(gate_proj).sum().item()}")
print(f" gate_base: shape={gate_base.shape}, NaN={torch.isnan(gate_base).sum().item()}")
print(f" gate_base range: [{gate_base.min().item():.4f}, {gate_base.max().item():.4f}]")
# Step 2: Gate LoRA
# intermediate = input @ lora_A^T: [num_tokens, hidden_size] @ [hidden_size, lora_rank] -> [num_tokens, lora_rank]
gate_lora_inter = local_input.float() @ gate_lora_a.float().T
print(f"\nStep 2a - Gate LoRA intermediate (input @ lora_A^T):")
print(f" gate_lora_a: shape={gate_lora_a.shape}, NaN={torch.isnan(gate_lora_a).sum().item()}")
print(f" gate_lora_inter: shape={gate_lora_inter.shape}, NaN={torch.isnan(gate_lora_inter).sum().item()}")
print(f" gate_lora_inter range: [{gate_lora_inter.min().item():.6f}, {gate_lora_inter.max().item():.6f}]")
# lora_out = intermediate @ lora_B^T: [num_tokens, lora_rank] @ [lora_rank, intermediate_size] -> [num_tokens, intermediate_size]
gate_lora_out = gate_lora_inter @ gate_lora_b.float().T
print(f"\nStep 2b - Gate LoRA output (inter @ lora_B^T):")
print(f" gate_lora_b: shape={gate_lora_b.shape}, NaN={torch.isnan(gate_lora_b).sum().item()}")
print(f" gate_lora_out: shape={gate_lora_out.shape}, NaN={torch.isnan(gate_lora_out).sum().item()}")
print(f" gate_lora_out range: [{gate_lora_out.min().item():.6f}, {gate_lora_out.max().item():.6f}]")
# Step 3: Add LoRA to base with scaling
gate_output = gate_base + gate_lora_out * lora_scaling
print(f"\nStep 3 - Gate output (base + lora * scaling):")
print(f" gate_output: shape={gate_output.shape}, NaN={torch.isnan(gate_output).sum().item()}")
print(f" gate_output range: [{gate_output.min().item():.4f}, {gate_output.max().item():.4f}]")
# 同样计算 Up
up_base = local_input.float() @ up_proj.float().T
up_lora_inter = local_input.float() @ up_lora_a.float().T
up_lora_out = up_lora_inter @ up_lora_b.float().T
up_output = up_base + up_lora_out * lora_scaling
print(f"\nUp projection 汇总:")
print(f" up_base NaN: {torch.isnan(up_base).sum().item()}")
print(f" up_lora_inter NaN: {torch.isnan(up_lora_inter).sum().item()}")
print(f" up_lora_out NaN: {torch.isnan(up_lora_out).sum().item()}")
print(f" up_output NaN: {torch.isnan(up_output).sum().item()}")
# Step 4: Activation
intermediate = silu(gate_output) * up_output
print(f"\nStep 4 - Activation (silu(gate) * up):")
print(f" intermediate: shape={intermediate.shape}, NaN={torch.isnan(intermediate).sum().item()}")
if torch.isnan(gate_output).sum().item() > 0:
# 详细分析 NaN 位置
nan_mask = torch.isnan(gate_output)
nan_indices = torch.nonzero(nan_mask)
print(f"\n*** 发现 NaN! ***")
print(f" NaN 数量: {nan_mask.sum().item()}")
print(f" 前 10 个 NaN 位置:")
for i in range(min(10, len(nan_indices))):
idx = nan_indices[i]
print(f" 位置 [{idx[0].item()}, {idx[1].item()}]")
return gate_output, up_output
def compare_with_other_experts(data, expert_token_map):
"""对比 Expert 17-24 与其他 Expert 的计算结果"""
print(f"\n{'='*70}")
print("对比不同 Expert 的计算结果")
print(f"{'='*70}")
problem_experts = list(range(17, 25))
other_experts = [e for e in expert_token_map.keys() if e not in problem_experts]
print(f"\n问题 Expert (17-24): {problem_experts}")
print(f"正常 Expert (采样): {other_experts[:5]}")
# 对比
print("\n对比各 Expert 的 Forward 计算:")
for expert_id in problem_experts[:2] + other_experts[:2]: # 各取 2 个
if expert_id in expert_token_map:
gate_out, up_out = manual_forward_for_expert(data, expert_token_map, expert_id)
def main():
print("=" * 70)
print("Expert 17-24 NaN 问题深度分析")
print("=" * 70)
# 1. 加载数据
data = load_and_analyze_data()
# 2. 分析 Expert 路由
expert_token_map = analyze_expert_routing(data)
# 3. 检查 Expert 17-24 的数据
print("\n" + "=" * 70)
print("检查 Expert 17-24 的原始数据")
print("=" * 70)
all_clean = True
for expert_id in range(17, 25):
is_clean = check_data_for_expert(data, expert_id)
all_clean = all_clean and is_clean
if all_clean:
print("\n*** Expert 17-24 的原始数据没有 NaN/Inf,问题可能在计算过程中 ***")
# 4. 手动计算并对比
print("\n" + "=" * 70)
print("手动执行 Forward 计算,逐步追踪 NaN")
print("=" * 70)
# 检查一个正常的 Expert
normal_expert = None
for e in expert_token_map.keys():
if e not in range(17, 25):
normal_expert = e
break
if normal_expert:
print(f"\n--- 正常 Expert {normal_expert} ---")
manual_forward_for_expert(data, expert_token_map, normal_expert)
# 检查一个问题 Expert
for e in [17, 20, 24]:
if e in expert_token_map:
print(f"\n--- 问题 Expert {e} ---")
manual_forward_for_expert(data, expert_token_map, e)
break
if __name__ == "__main__":
main()