Skip to content

Commit b52eb9a

Browse files
authored
fix rulse for fused swiglu probs bwd (#700)
1 parent 9a875d7 commit b52eb9a

1 file changed

Lines changed: 45 additions & 69 deletions

File tree

tester/paddle_to_torch/rules.py

Lines changed: 45 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8340,86 +8340,62 @@ def apply(self, paddle_api: str) -> ConvertResult:
83408340
83418341
elif op_name in ("fused_swiglu_probs_bwd", "paddlefleet_fused_swiglu_probs_bwd"):
83428342
# _run_custom_op("fused_swiglu_probs_bwd", o1, do2_s, unzipped_probs, inplace)
8343-
# 输出 [do1, probs_grad, o2_s],语义参考 paddlefleet 的 SwigluProbsGradKernel:
8344-
# lhs, rhs = chunk(o1, 2, -1); sig = sigmoid(lhs); silu = sig*lhs
8345-
# do1[..., :H] = (do2_s*probs) * rhs * sig * (1 + lhs - silu)
8346-
# do1[..., H:] = (do2_s*probs) * silu
8347-
# o2_s = silu * rhs * probs
8348-
# probs_grad = sum_last_dim(do2_s * silu * rhs),shape [outer_dim],float32
8349-
# 注意:
8350-
# 1) 空输入 (numel==0) 时 Paddle 直接返回占位 tensor,不做 shape 一致性检查;
8351-
# 2) 大 shape + bf16 输入若整体 upcast 到 fp32 会爆显存,分块处理。
8352-
o1 = arg1
8353-
do2_s = arg2
8354-
probs = arg3
8343+
# 对照 paddlefleet_ops/_extensions/fused_swiglu_probs_bwd.cu 中
8344+
# SwigluProbsGradKernel 的公式逐句实现,全程 fp32、只在写出时舍入一次;
8345+
# 乘法结合顺序与 kernel 保持一致(不做代数重排):
8346+
# lhs = o1[..., :H]; rhs = o1[..., H:]
8347+
# sig = 1/(1+exp(-lhs)); tmp = sig*lhs; o2_val = tmp*rhs
8348+
# do2_val = do2_s*prob
8349+
# do1[..., :H] = do2_val*rhs*sig*(1+lhs-tmp)
8350+
# do1[..., H:] = do2_val*tmp
8351+
# o2_s = o2_val*prob
8352+
# probs_grad = sum(do2_s*o2_val),fp32,shape [outer]
8353+
o1 = arg1
8354+
do2_s = arg2
8355+
probs = arg3
83558356
inplace_flag = bool(arg4) if arg4 is not None else False
8356-
H2 = o1.shape[-1]
8357-
H = H2 // 2
8357+
H = o1.shape[-1] // 2
83588358
outer = 1
83598359
for _s in o1.shape[:-1]:
83608360
outer *= _s
83618361
8362-
# Paddle 的 SwigluProbsGradCUDABackward:
8363-
# do1 = inplace ? o1 : empty_like(o1)
8364-
# o2_s = inplace ? do2_s : empty_like(do2_s)
8365-
# probs_grad = empty({outer}, fp32) # 始终新分配
8366-
# 当任一输入 numel==0 时,kernel 不执行,直接返回上述(已分配但未写入的)buffer。
8367-
# 因此 inplace=True 时 do1/o2_s 保留 o1/do2_s 的原值;非 inplace 时为未初始化值。
8362+
# SwigluProbsGradCUDABackward 在任一输入 numel==0 时不启动 kernel,直接返回
8363+
# do1 = inplace ? o1 : empty_like(o1)、o2_s = inplace ? do2_s : empty_like(do2_s)
8364+
# 以及始终新分配的 probs_grad = empty({outer}, fp32)(未初始化)。
83688365
if o1.numel() == 0 or do2_s.numel() == 0 or probs.numel() == 0:
83698366
if inplace_flag:
8370-
do1_e = o1.clone()
8371-
o2_s_e = do2_s.clone()
8367+
do1 = o1.clone()
8368+
o2_s = do2_s.clone()
83728369
else:
8373-
do1_e = torch.zeros_like(o1)
8374-
o2_s_e = torch.zeros_like(do2_s)
8375-
# probs_grad 始终是新分配的未初始化 buffer;用 0 占位
8376-
pg_e = torch.zeros([outer], dtype=torch.float32, device=o1.device)
8377-
result = [do1_e, pg_e, o2_s_e]
8370+
do1 = torch.zeros_like(o1)
8371+
o2_s = torch.zeros_like(do2_s)
8372+
probs_grad = torch.zeros([outer], dtype=torch.float32, device=o1.device)
8373+
result = [do1, probs_grad, o2_s]
83788374
else:
8379-
o1_dtype = o1.dtype
8380-
do2_dtype = do2_s.dtype
8381-
o1_2d = o1.reshape(outer, H2)
8382-
do2_2d = do2_s.reshape(outer, H)
8383-
probs_flat = probs.reshape(-1)
8384-
# Accuracy tests create Paddle/Torch input pairs through DLPack, so the
8385-
# two framework tensors can share storage. Even when the custom op asks
8386-
# Paddle to reuse its inputs, the reference must preserve its inputs for
8387-
# the subsequent Paddle run. Chunked temporaries still bound the peak.
8388-
do1_out = torch.empty_like(o1_2d)
8389-
o2_s_out = torch.empty_like(do2_2d)
8390-
pg_out = torch.empty([outer], dtype=torch.float32, device=o1.device)
8391-
# At most nine FP32 [chunk, H] buffers overlap below. Include BF16
8392-
# casts so the temporary working set remains within 32 GiB.
8393-
_workspace_bytes = 32 << 30
8394-
bytes_per_row = max(1, H * (4 * 9 + o1.element_size() * 3))
8395-
row_chunk = max(1, min(outer, _workspace_bytes // bytes_per_row))
8396-
# 切片原地写入会触发 autograd 对叶子张量的报错(尤其 fp32 叶子节点),
8397-
# 这里整体走 no_grad:本算子是 bwd kernel 的数值复刻,不需要再次求导。
8375+
o1_2d = o1.reshape(outer, H * 2)
8376+
# 本算子是 bwd kernel 的数值复刻,不需要再次求导;同时 inplace=True 时
8377+
# paddle 会写坏共享存储的输入,参考实现一律输出新 buffer。
83988378
with torch.no_grad():
8399-
for row_start in range(0, outer, row_chunk):
8400-
row_end = min(outer, row_start + row_chunk)
8401-
# inplace 时 o1_2d 即将被写入,需先把 lhs/rhs 拷到 fp32 中间变量
8402-
lhs_c = o1_2d[row_start:row_end, :H].float()
8403-
rhs_c = o1_2d[row_start:row_end, H:].float()
8404-
do2_c = do2_2d[row_start:row_end].float()
8405-
prob_c = probs_flat[row_start:row_end].to(torch.float32).unsqueeze(-1)
8406-
sig_c = torch.sigmoid(lhs_c)
8407-
silu_c = lhs_c * sig_c
8408-
o2_c = silu_c * rhs_c
8409-
8410-
pg_out[row_start:row_end] = (do2_c * o2_c).sum(dim=-1)
8411-
o2_s_out[row_start:row_end] = (o2_c * prob_c).to(do2_dtype)
8412-
8413-
# Reuse do2_c for the probability-weighted upstream gradient.
8414-
do2_c.mul_(prob_c)
8415-
x1g_c = do2_c * silu_c
8416-
lhs_c.sub_(silu_c).add_(1.0).mul_(sig_c).mul_(rhs_c).mul_(do2_c)
8417-
do1_out[row_start:row_end, :H] = lhs_c.to(o1_dtype)
8418-
do1_out[row_start:row_end, H:] = x1g_c.to(o1_dtype)
8379+
lhs = o1_2d[:, :H].float()
8380+
rhs = o1_2d[:, H:].float()
8381+
do2 = do2_s.reshape(outer, H).float()
8382+
prob = probs.reshape(outer, 1).to(torch.float32)
8383+
8384+
sig = torch.sigmoid(lhs)
8385+
tmp = sig * lhs
8386+
o2_val = tmp * rhs
8387+
do2_val = do2 * prob
8388+
8389+
x0_grad = do2_val * rhs * sig * (1.0 + lhs - tmp)
8390+
x1_grad = do2_val * tmp
8391+
8392+
do1 = torch.cat([x0_grad, x1_grad], dim=-1).to(o1.dtype)
8393+
o2_s = (o2_val * prob).to(do2_s.dtype)
8394+
probs_grad = (do2 * o2_val).sum(dim=-1)
84198395
result = [
8420-
do1_out.reshape(o1.shape),
8421-
pg_out,
8422-
o2_s_out.reshape(do2_s.shape),
8396+
do1.reshape(o1.shape),
8397+
probs_grad,
8398+
o2_s.reshape(do2_s.shape),
84238399
]
84248400
84258401
elif op_name == "fuse_weighted_swiglu_fp8_quant":

0 commit comments

Comments
 (0)