@@ -8340,86 +8340,62 @@ def apply(self, paddle_api: str) -> ConvertResult:
83408340
83418341elif 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
84258401elif op_name == "fuse_weighted_swiglu_fp8_quant":
0 commit comments