Fix KV merge: use NORMALIZED O (O_unnorm/row_sum) with LSE
This commit is contained in:
@@ -165,6 +165,7 @@ def _attention_single_head(
|
||||
|
||||
seg_o = torch.zeros(T, hd, dtype=torch.float32, device='cuda')
|
||||
seg_lse = torch.zeros(T, 1, dtype=torch.float32, device='cuda')
|
||||
seg_row_sums = torch.zeros(T, 1, dtype=torch.float32, device='cuda')
|
||||
|
||||
for nt in range(n_pv_tiles):
|
||||
v_start = nt * pv_n_tile
|
||||
@@ -173,34 +174,38 @@ def _attention_single_head(
|
||||
v_kernel = v_tile.unsqueeze(-1)
|
||||
c_tile = torch.zeros(T, pv_n_tile, 1, dtype=torch.bfloat16, device='cuda')
|
||||
lse_tensor = torch.zeros(T, 1, 1, dtype=torch.float32, device='cuda')
|
||||
row_sums_tensor = torch.zeros(T, 1, 1, dtype=torch.float32, device='cuda')
|
||||
|
||||
mQ = ct.from_dlpack(q_3d).mark_layout_dynamic(leading_dim=ct.get_leading_dim(q_3d))
|
||||
mK = ct.from_dlpack(k_seg).mark_layout_dynamic(leading_dim=ct.get_leading_dim(k_seg))
|
||||
mV = ct.from_dlpack(v_kernel).mark_layout_dynamic(leading_dim=ct.get_leading_dim(v_kernel))
|
||||
mC = ct.from_dlpack(c_tile).mark_layout_dynamic(leading_dim=ct.get_leading_dim(c_tile))
|
||||
mLSE = ct.from_dlpack(lse_tensor).mark_layout_dynamic(leading_dim=ct.get_leading_dim(lse_tensor))
|
||||
mRS = ct.from_dlpack(row_sums_tensor).mark_layout_dynamic(leading_dim=ct.get_leading_dim(row_sums_tensor))
|
||||
|
||||
compiled(mQ, mK, mV, mC, stream, lse=mLSE)
|
||||
compiled(mQ, mK, mV, mC, stream, lse=mLSE, row_sums=mRS)
|
||||
torch.cuda.synchronize()
|
||||
|
||||
seg_o[:, v_start:v_end] = c_tile[:, :, 0].float()
|
||||
if nt == 0:
|
||||
seg_lse[:, 0] = lse_tensor[:, 0, 0].float()
|
||||
seg_row_sums[:, 0] = row_sums_tensor[:, 0, 0].float()
|
||||
|
||||
# Merge with accumulator using log-sum-exp
|
||||
# Normalize segment output: O_norm = O_unnorm / row_sum (per row)
|
||||
seg_row_sums = seg_row_sums.clamp(min=1e-30)
|
||||
seg_o_norm = seg_o / seg_row_sums # (T, hd) normalized
|
||||
|
||||
# Merge with accumulator using CORRECT formula:
|
||||
# O = sum(exp(lse_i) * O_i_norm) / sum(exp(lse_i))
|
||||
# where O_i_norm = O_i_unnorm / row_sum_i
|
||||
#
|
||||
# o_accum is tracked in NORMALIZED form.
|
||||
e_old = torch.exp(lse_accum)
|
||||
e_new = torch.exp(seg_lse)
|
||||
e_sum = e_old + e_new
|
||||
|
||||
o_accum = (e_old * o_accum + e_new * seg_o) / e_sum
|
||||
o_accum = (e_old * o_accum + e_new * seg_o_norm) / e_sum
|
||||
lse_accum = torch.log(e_sum)
|
||||
|
||||
# Debug: check LSE values
|
||||
if seg == 0:
|
||||
print(f' seg 0: lse[0]={seg_lse[0,0].item():.4f}, o[0,0]={seg_o[0,0].item():.4f}')
|
||||
elif seg == 1:
|
||||
print(f' seg 1: lse[0]={seg_lse[0,0].item():.4f}, o[0,0]={seg_o[0,0].item():.4f}')
|
||||
print(f' merged: lse[0]={lse_accum[0,0].item():.4f}, o[0,0]={o_accum[0,0].item():.4f}')
|
||||
|
||||
output = o_accum.to(torch.bfloat16).unsqueeze(0)
|
||||
return output
|
||||
|
||||
Reference in New Issue
Block a user