Skip to content

Commit 5ed4104

Browse files
authored
fix rules for fused swiglu scale clamp both forward and backward (#698)
1 parent a6d158e commit 5ed4104

1 file changed

Lines changed: 68 additions & 59 deletions

File tree

tester/paddle_to_torch/rules.py

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8253,80 +8253,89 @@ def apply(self, paddle_api: str) -> ConvertResult:
82538253
82548254
elif op_name == "fused_swiglu_scale_clamp":
82558255
# _run_custom_op("fused_swiglu_scale_clamp", x, scale, max_val)
8256-
# fwd: split x -> (x1, x2); out = clamp(silu(x1) * x2 * scale, -max_val, max_val)
8257-
# Paddle custom op returns a list; we return [out] to match.
8256+
# clamp 语义对齐 PaddleFleet fusions/fused_swiglu_scale.py::
8257+
# fused_swiglu_scale_forward (gate 只截上限, value 对称截断),
8258+
# 精度顺序对齐 CUDA kernel VectorizedFusedSwiGLUFwd 以做到 bit 级一致:
8259+
# g = min(gate, cv); v = clamp(value, -cv, cv)
8260+
# out = (T)((g * sigmoid(g)) * v * s) # 全程 fp32, 只在最后舍入一次
8261+
# 注意: fleet 的 CPU/XPU fallback 写成
8262+
# (silu(g)*v).cast(x.dtype) * scale.cast(x.dtype)
8263+
# 比 kernel 多两次 bf16 舍入, 与 CUDA 算子本身不 bit 一致, 故这里按 kernel 写。
82588264
x = arg1 # shape [..., 2D]
82598265
scale = arg2 # scalar or tensor [..., 1] or [...,]
82608266
max_val = arg3 # scalar
8267+
_cv = float(max_val)
82618268
_hidden = x.shape[-1] // 2
8262-
if x.shape[-1] == 0:
8263-
result = [torch.empty((*x.shape[:-1], 0), dtype=x.dtype, device=x.device)]
8264-
elif x.shape[-1] % 2 != 0:
8265-
raise ValueError(
8266-
f"input shape is invalid for input of size {tuple(x.shape)}: "
8267-
"fused_swiglu_scale_clamp requires an even last dimension"
8268-
)
8269+
_x_fp32 = x.to(torch.float32)
8270+
_gate = torch.clamp(_x_fp32[..., :_hidden], max=_cv)
8271+
_val = torch.clamp(_x_fp32[..., _hidden:], min=-_cv, max=_cv)
8272+
if torch.is_tensor(scale):
8273+
_scale_exp = scale.to(torch.float32)
82698274
else:
8270-
_outer = x.numel() // (_hidden * 2)
8271-
_x_2d = x.reshape(_outer, _hidden * 2)
8272-
_out = torch.empty((_outer, _hidden), dtype=x.dtype, device=x.device)
8273-
if torch.is_tensor(scale):
8274-
scale = scale.view(*scale.shape[:-1], 1) if scale.dim() > 1 else scale.unsqueeze(-1)
8275-
_workspace_bytes = 32 << 30
8276-
_bytes_per_row = max(1, _hidden * (4 * 4 + x.element_size()))
8277-
_row_chunk = max(1, min(_outer, _workspace_bytes // _bytes_per_row))
8278-
with torch.no_grad():
8279-
for _row_start in range(0, _outer, _row_chunk):
8280-
_row_end = min(_outer, _row_start + _row_chunk)
8281-
_lhs = _x_2d[_row_start:_row_end, :_hidden].to(torch.float32)
8282-
_rhs = _x_2d[_row_start:_row_end, _hidden:].to(torch.float32)
8283-
_lhs.mul_(torch.sigmoid(_lhs)).mul_(_rhs)
8284-
_scale_chunk = scale[_row_start:_row_end] if torch.is_tensor(scale) and scale.numel() > 1 else scale
8285-
_lhs.mul_(_scale_chunk).clamp_(min=-float(max_val), max=float(max_val))
8286-
_out[_row_start:_row_end] = _lhs.to(x.dtype)
8287-
result = [_out.reshape(*x.shape[:-1], _hidden)]
8275+
_scale_exp = torch.tensor(float(scale), dtype=torch.float32, device=x.device)
8276+
while _scale_exp.dim() < _gate.dim():
8277+
_scale_exp = _scale_exp.unsqueeze(-1)
8278+
_swiglu = (_gate * torch.sigmoid(_gate)) * _val
8279+
result = [(_swiglu * _scale_exp).to(x.dtype)]
82888280
82898281
82908282
elif op_name == "fused_swiglu_scale_clamp_bwd":
82918283
# _run_custom_op("fused_swiglu_scale_clamp_bwd", x, scale, dy, max_val)
8292-
# arg1=x (original fwd input [..., 2D]), arg2=scale, arg3=dy (grad of output [..., D]),
8293-
# arg4=max_val. Returns [dx, d_scale] to match Paddle output.
8284+
# clamp 语义对齐 PaddleFleet fusions/fused_swiglu_scale.py::
8285+
# fused_swiglu_scale_backward, 精度/舍入点对齐 CUDA kernel
8286+
# VectorizedFusedSwiGLUBwd (kHasClamp=true):
8287+
# g_eff = min(g, cv); v_eff = clamp(v, -cv, cv)
8288+
# g_mask = (g <= cv); v_mask = (-cv <= v <= cv) # fp32 掩码, 边界处梯度通过
8289+
# d_u = dout * s # 全程 fp32
8290+
# d_v = (T)(d_u * silu_g * v_mask)
8291+
# d_g = (T)(d_u * sig * (1 + g_eff*(1-sig)) * v_eff * g_mask)
8292+
# d_scale = (ScaleT)sum_fp32( (T)swiglu * (ScaleT)dout )
8293+
# kernel 特意把 fp32 swiglu 先压回 x.dtype 再与 dout 相乘, 用 fp32 累加
82948294
x = arg1 # shape [..., 2D] (original forward input)
82958295
scale = arg2 # scalar or tensor [..., 1]
82968296
dy = arg3 # shape [..., D] (gradient of forward output)
82978297
max_val = arg4 # scalar
8298-
_hidden = dy.shape[-1]
8299-
_outer = dy.numel() // _hidden
8300-
_x_2d = x.reshape(_outer, _hidden * 2)
8301-
_dy_2d = dy.reshape(_outer, _hidden)
8302-
_dx = torch.empty_like(_x_2d)
8303-
_d_scale = torch.empty((_outer, 1), dtype=torch.float32, device=x.device)
8298+
_cv = float(max_val)
8299+
_hidden = x.shape[-1] // 2
8300+
_x_fp32 = x.to(torch.float32)
8301+
_gate_raw = _x_fp32[..., :_hidden]
8302+
_val_raw = _x_fp32[..., _hidden:]
8303+
_gate = torch.clamp(_gate_raw, max=_cv)
8304+
_val = torch.clamp(_val_raw, min=-_cv, max=_cv)
8305+
# kernel 中掩码是 float, 保持 fp32 避免额外的 dtype 提升
8306+
_g_mask = (_gate_raw <= _cv).to(torch.float32)
8307+
_v_mask = ((_val_raw <= _cv) & (_val_raw >= -_cv)).to(torch.float32)
8308+
_sig = torch.sigmoid(_gate)
8309+
_silu = _gate * _sig
8310+
_swiglu_val = _silu * _val
83048311
if torch.is_tensor(scale):
8305-
scale_v = scale.view(*scale.shape[:-1], 1) if scale.dim() > 1 else scale.unsqueeze(-1)
8312+
_scale_dtype = scale.dtype
8313+
_scale_exp = scale.to(torch.float32)
83068314
else:
8307-
scale_v = float(scale)
8308-
_workspace_bytes = 32 << 30
8309-
_bytes_per_row = max(1, _hidden * (4 * 10 + x.element_size() * 2))
8310-
_row_chunk = max(1, min(_outer, _workspace_bytes // _bytes_per_row))
8311-
with torch.no_grad():
8312-
for _row_start in range(0, _outer, _row_chunk):
8313-
_row_end = min(_outer, _row_start + _row_chunk)
8314-
_lhs = _x_2d[_row_start:_row_end, :_hidden].to(torch.float32)
8315-
_rhs = _x_2d[_row_start:_row_end, _hidden:].to(torch.float32)
8316-
_dy = _dy_2d[_row_start:_row_end].to(torch.float32)
8317-
_scale_chunk = scale_v[_row_start:_row_end] if torch.is_tensor(scale_v) and scale_v.numel() > 1 else scale_v
8318-
_sigmoid = torch.sigmoid(_lhs)
8319-
_silu = _lhs * _sigmoid
8320-
_pre_clamp = _silu * _rhs * _scale_chunk
8321-
_dy.mul_((_pre_clamp.abs() < float(max_val)).to(torch.float32))
8322-
_d_scale[_row_start:_row_end] = (_dy * _silu * _rhs).sum(dim=-1, keepdim=True)
8323-
_dx[_row_start:_row_end, _hidden:] = (_dy * _silu * _scale_chunk).to(x.dtype)
8324-
_lhs.sub_(_silu).add_(1.0).mul_(_sigmoid).mul_(_rhs).mul_(_dy).mul_(_scale_chunk)
8325-
_dx[_row_start:_row_end, :_hidden] = _lhs.to(x.dtype)
8326-
dx = _dx.reshape(x.shape)
8327-
d_scale = _d_scale.reshape(*x.shape[:-1], 1)
8328-
if torch.is_tensor(scale):
8329-
d_scale = d_scale.to(scale.dtype)
8315+
_scale_dtype = torch.float32
8316+
_scale_exp = torch.tensor(float(scale), dtype=torch.float32, device=x.device)
8317+
while _scale_exp.dim() < dy.dim():
8318+
_scale_exp = _scale_exp.unsqueeze(-1)
8319+
_d_u = dy * _scale_exp
8320+
_d_val = _d_u * _silu * _v_mask
8321+
# kernel 里 1.0f + g_eff*(1.0f-sig) 会被 nvcc 收缩成单条 FMA(只舍入一次),
8322+
# 用 addcmul 走同一条 fused 路径, 而不是 mul+add 两次舍入。
8323+
_d_gate = (
8324+
_d_u
8325+
* _sig
8326+
* torch.addcmul(torch.ones((), dtype=torch.float32, device=x.device), _gate, 1.0 - _sig)
8327+
* _val
8328+
* _g_mask
8329+
)
8330+
dx = torch.cat([_d_gate, _d_val], dim=-1).to(x.dtype)
8331+
# d_scale: fp32 swiglu 先压回 x.dtype, dout 转 scale dtype, 乘积用 fp32 累加,
8332+
# 最后一次性 cast 回 scale dtype(与 kernel 的 shared float 归约一致)
8333+
d_scale = (
8334+
(_swiglu_val.to(x.dtype) * dy.to(_scale_dtype))
8335+
.to(torch.float32)
8336+
.sum(dim=-1, keepdim=True)
8337+
.to(_scale_dtype)
8338+
)
83308339
result = [dx, d_scale]
83318340
83328341
elif op_name in ("fused_swiglu_probs_bwd", "paddlefleet_fused_swiglu_probs_bwd"):

0 commit comments

Comments
 (0)