Skip to content

Commit 2d9b5a4

Browse files
committed
Update
1 parent bd67af5 commit 2d9b5a4

17 files changed

Lines changed: 3520 additions & 113 deletions

analyze_compression_diff.py

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
分析 compression branch 的 naive 和 triton 实现差异
5+
重点关注为什么 backward dq 的值不同
6+
"""
7+
8+
import os
9+
import torch
10+
import triton
11+
12+
os.environ['TRITON_F32_DEFAULT'] = 'ieee'
13+
14+
from native_sparse_attention.ops.parallel import parallel_nsa_compression
15+
from fla.ops.utils import mean_pooling
16+
17+
18+
def analyze_forward_differences():
19+
"""分析 forward 的实现差异"""
20+
print("="*80)
21+
print("分析 Forward 实现差异")
22+
print("="*80)
23+
24+
B, T, H, HQ, D = 1, 256, 4, 64, 64
25+
block_size = 32
26+
dtype = torch.bfloat16
27+
scale = 0.1
28+
29+
torch.manual_seed(42)
30+
31+
# 创建输入
32+
q = torch.randn((B, T, HQ, D), dtype=dtype, device='cuda')
33+
k = torch.randn((B, T, H, D), dtype=dtype, device='cuda')
34+
v = torch.randn((B, T, H, D), dtype=dtype, device='cuda')
35+
36+
# Compression
37+
k_cmp = mean_pooling(k, block_size, None)
38+
v_cmp = mean_pooling(v, block_size, None)
39+
40+
print(f"\n1. 输入形状:")
41+
print(f" q: {q.shape}")
42+
print(f" k: {k.shape} -> k_cmp: {k_cmp.shape}")
43+
print(f" v: {v.shape} -> v_cmp: {v_cmp.shape}")
44+
45+
# Triton 实现
46+
print(f"\n2. Triton 实现 (parallel_nsa_compression):")
47+
print(f" - 使用优化的 Triton kernel")
48+
print(f" - Forward kernel: parallel_nsa_compression_fwd_kernel")
49+
print(f" - 特点:")
50+
print(f" * 在线计算 softmax (online softmax algorithm)")
51+
print(f" * 使用 log-sum-exp trick 保持数值稳定")
52+
print(f" * 分块处理,减少内存访问")
53+
54+
o_tri, lse_tri = parallel_nsa_compression(
55+
q=q, k=k_cmp, v=v_cmp,
56+
block_size=block_size, scale=scale, offsets=None
57+
)
58+
59+
print(f" - 输出: o shape={o_tri.shape}, lse shape={lse_tri.shape}")
60+
print(f" - o 的范围: [{o_tri.min().item():.4f}, {o_tri.max().item():.4f}]")
61+
print(f" - lse 的范围: [{lse_tri.min().item():.4f}, {lse_tri.max().item():.4f}]")
62+
63+
# Naive 实现 (手工实现标准attention)
64+
print(f"\n3. Naive 实现 (手工标准 attention):")
65+
print(f" - 使用 PyTorch 原生操作")
66+
print(f" - 直接计算: softmax(Q @ K^T) @ V")
67+
68+
# 手工实现 compression attention
69+
G = HQ // H
70+
k_cmp_expanded = k_cmp.repeat_interleave(G, dim=2) # [B, C, HQ, D]
71+
v_cmp_expanded = v_cmp.repeat_interleave(G, dim=2)
72+
73+
q_float = q.float()
74+
k_cmp_float = k_cmp_expanded.float()
75+
v_cmp_float = v_cmp_expanded.float()
76+
77+
# 重排维度用于 attention: [B, HQ, T, D]
78+
q_attn = q_float.transpose(1, 2) # [B, HQ, T, D]
79+
k_attn = k_cmp_float.transpose(1, 2) # [B, HQ, C, D]
80+
v_attn = v_cmp_float.transpose(1, 2) # [B, HQ, C, D]
81+
82+
# Attention scores
83+
C = k_cmp.shape[1]
84+
attn_scores = torch.matmul(q_attn * scale, k_attn.transpose(-2, -1)) # [B, HQ, T, C]
85+
86+
# Causal mask
87+
causal_mask = ((torch.arange(T, device='cuda') - block_size + 1)[:, None] // block_size
88+
< torch.arange(C, device='cuda')[None, :])
89+
empty_mask = causal_mask.all(-1, keepdim=True)
90+
91+
attn_scores = attn_scores.masked_fill(causal_mask.unsqueeze(0).unsqueeze(0), float('-inf'))
92+
attn_probs = torch.softmax(attn_scores, dim=-1)
93+
attn_probs = attn_probs.masked_fill(empty_mask.unsqueeze(0).unsqueeze(0), 0.0)
94+
95+
o_naive = torch.matmul(attn_probs, v_attn) # [B, HQ, T, D]
96+
o_naive = o_naive.transpose(1, 2).to(dtype) # [B, T, HQ, D]
97+
98+
# LSE for naive
99+
lse_naive = torch.logsumexp(attn_scores, dim=-1).to(torch.float32) # [B, HQ, T]
100+
lse_naive = lse_naive.transpose(1, 2) # [B, T, HQ]
101+
102+
print(f" - 输出: o shape={o_naive.shape}, lse shape={lse_naive.shape}")
103+
print(f" - o 的范围: [{o_naive.min().item():.4f}, {o_naive.max().item():.4f}]")
104+
print(f" - lse 的范围: [{lse_naive.min().item():.4f}, {lse_naive.max().item():.4f}]")
105+
106+
# 比较
107+
print(f"\n4. Forward 输出对比:")
108+
o_diff = (o_tri - o_naive).abs()
109+
lse_diff = (lse_tri - lse_naive).abs()
110+
111+
print(f" - o 最大差异: {o_diff.max().item():.6f}")
112+
print(f" - o 平均差异: {o_diff.mean().item():.6f}")
113+
print(f" - o 相对误差: {(o_diff.mean() / o_naive.abs().mean()).item():.6f}")
114+
115+
print(f" - lse 最大差异: {lse_diff.max().item():.6f}")
116+
print(f" - lse 平均差异: {lse_diff.mean().item():.6f}")
117+
118+
return q, k_cmp, v_cmp, o_tri, o_naive, lse_tri, lse_naive
119+
120+
121+
def analyze_backward_differences(q, k_cmp, v_cmp, o_tri, o_naive, lse_tri, lse_naive):
122+
"""分析 backward 的实现差异"""
123+
print("\n" + "="*80)
124+
print("分析 Backward 实现差异")
125+
print("="*80)
126+
127+
B, T, HQ, D = q.shape
128+
H = k_cmp.shape[2]
129+
G = HQ // H
130+
block_size = 32
131+
scale = 0.1
132+
133+
# 创建梯度输出
134+
do = torch.randn_like(o_tri)
135+
136+
print(f"\n1. Backward 输入:")
137+
print(f" - do shape: {do.shape}")
138+
print(f" - do 的范围: [{do.min().item():.4f}, {do.max().item():.4f}]")
139+
140+
# Triton backward
141+
print(f"\n2. Triton Backward:")
142+
print(f" - 使用 parallel_nsa_compression_bwd_kernel_dq")
143+
print(f" - 关键步骤:")
144+
print(f" * delta = sum(do * o, dim=-1) # [B, T, HQ]")
145+
print(f" * p = exp(s - lse) # 重新计算 attention probs")
146+
print(f" * ds = p * (dp - delta)")
147+
print(f" * dq = ds @ K^T * scale")
148+
print(f" - 特点: 使用 FlashAttention-style backward")
149+
150+
q_tri = q.clone().requires_grad_(True)
151+
k_cmp_tri = k_cmp.detach()
152+
v_cmp_tri = v_cmp.detach()
153+
154+
o_tri_new, _ = parallel_nsa_compression(
155+
q=q_tri, k=k_cmp_tri, v=v_cmp_tri,
156+
block_size=block_size, scale=scale, offsets=None
157+
)
158+
o_tri_new.backward(do)
159+
dq_tri = q_tri.grad.clone()
160+
161+
print(f" - dq shape: {dq_tri.shape}")
162+
print(f" - dq 的范围: [{dq_tri.min().item():.4f}, {dq_tri.max().item():.4f}]")
163+
print(f" - dq norm: {dq_tri.norm().item():.6f}")
164+
165+
# Naive backward
166+
print(f"\n3. Naive Backward (PyTorch autograd):")
167+
print(f" - 使用 PyTorch 自动微分")
168+
print(f" - 标准 attention backward公式")
169+
170+
q_naive = q.clone().requires_grad_(True)
171+
k_cmp_naive = k_cmp.clone().detach()
172+
v_cmp_naive = v_cmp.clone().detach()
173+
174+
# 重新计算 forward
175+
k_cmp_expanded = k_cmp_naive.repeat_interleave(G, dim=2)
176+
v_cmp_expanded = v_cmp_naive.repeat_interleave(G, dim=2)
177+
178+
q_float = q_naive.float()
179+
k_float = k_cmp_expanded.float()
180+
v_float = v_cmp_expanded.float()
181+
182+
q_attn = q_float.transpose(1, 2)
183+
k_attn = k_float.transpose(1, 2)
184+
v_attn = v_float.transpose(1, 2)
185+
186+
C = k_cmp_naive.shape[1]
187+
attn_scores = torch.matmul(q_attn * scale, k_attn.transpose(-2, -1))
188+
189+
causal_mask = ((torch.arange(T, device='cuda') - block_size + 1)[:, None] // block_size
190+
< torch.arange(C, device='cuda')[None, :])
191+
empty_mask = causal_mask.all(-1, keepdim=True)
192+
193+
attn_scores = attn_scores.masked_fill(causal_mask.unsqueeze(0).unsqueeze(0), float('-inf'))
194+
attn_probs = torch.softmax(attn_scores, dim=-1)
195+
attn_probs = attn_probs.masked_fill(empty_mask.unsqueeze(0).unsqueeze(0), 0.0)
196+
197+
o_naive_new = torch.matmul(attn_probs, v_attn)
198+
o_naive_new = o_naive_new.transpose(1, 2).to(q_naive.dtype)
199+
200+
o_naive_new.backward(do)
201+
dq_naive = q_naive.grad.clone()
202+
203+
print(f" - dq shape: {dq_naive.shape}")
204+
print(f" - dq 的范围: [{dq_naive.min().item():.4f}, {dq_naive.max().item():.4f}]")
205+
print(f" - dq norm: {dq_naive.norm().item():.6f}")
206+
207+
# 比较
208+
print(f"\n4. Backward dq 对比:")
209+
dq_diff = (dq_tri - dq_naive).abs()
210+
211+
print(f" - 最大差异: {dq_diff.max().item():.6f}")
212+
print(f" - 平均差异: {dq_diff.mean().item():.6f}")
213+
print(f" - 相对误差: {(dq_diff.norm() / dq_naive.norm()).item():.6f}")
214+
print(f" - Norm 比率 (tri/naive): {(dq_tri.norm() / dq_naive.norm()).item():.6f}")
215+
216+
# 详细分析差异来源
217+
print(f"\n5. 差异来源分析:")
218+
print(f" 可能的原因:")
219+
print(f" a) 数值精度: Triton kernel 使用 bfloat16, naive 使用 float32")
220+
print(f" b) 计算顺序: Triton 的在线算法 vs PyTorch 的批量计算")
221+
print(f" c) Softmax 实现: FlashAttention-style vs 标准 softmax")
222+
print(f" d) 舍入误差累积: 分块计算可能导致不同的舍入模式")
223+
224+
# 检查 attention 模式是否一致
225+
print(f"\n6. 检查 attention 模式:")
226+
with torch.no_grad():
227+
q_test = q.float()
228+
k_test = k_cmp_expanded.float()
229+
230+
q_test_attn = q_test.transpose(1, 2)
231+
k_test_attn = k_test.transpose(1, 2)
232+
233+
scores_test = torch.matmul(q_test_attn * scale, k_test_attn.transpose(-2, -1))
234+
scores_test = scores_test.masked_fill(causal_mask.unsqueeze(0).unsqueeze(0), float('-inf'))
235+
probs_test = torch.softmax(scores_test, dim=-1)
236+
237+
# 检查几个样本位置的 attention 分布
238+
for t in [50, 100, 150, 200]:
239+
print(f" 位置 t={t}:")
240+
prob_sample = probs_test[0, 0, t, :t//block_size]
241+
print(f" - Top-5 attention weights: {prob_sample.topk(5)[0].cpu().numpy()}")
242+
print(f" - Entropy: {-(prob_sample * torch.log(prob_sample + 1e-10)).sum().item():.4f}")
243+
244+
245+
if __name__ == '__main__':
246+
q, k_cmp, v_cmp, o_tri, o_naive, lse_tri, lse_naive = analyze_forward_differences()
247+
analyze_backward_differences(q, k_cmp, v_cmp, o_tri, o_naive, lse_tri, lse_naive)
248+
249+
print("\n" + "="*80)
250+
print("结论:")
251+
print("="*80)
252+
print("compression branch 的 backward dq 存在显著差异,主要原因可能是:")
253+
print("1. Triton kernel 的在线 softmax 算法与标准实现的数值差异")
254+
print("2. 混合精度计算带来的累积误差")
255+
print("3. 分块计算的舍入误差传播")
256+
print("\n建议检查 Triton backward kernel 的实现是否正确匹配 forward 的计算逻辑")

0 commit comments

Comments
 (0)